Provisioning: Align frontend more with designs (#102687)
* feat: align frontend more with designs * feat: remove unnecessary fields * chore: remove imports * chore: move imports * fix: make label not spit out undefined * feat: update title of nav item * chore: make update-workspace * fix: don't wrap text with span unless required --------- Co-authored-by: Roberto Jimenez Sanchez <roberto.jimenez@grafana.com>
This commit is contained in:
co-authored by
Roberto Jimenez Sanchez
parent
f73f88e0f3
commit
75ce8db0e2
@@ -15,6 +15,8 @@ export interface FieldProps extends HTMLAttributes<HTMLDivElement> {
|
||||
children: React.ReactElement;
|
||||
/** Label for the field */
|
||||
label?: React.ReactNode;
|
||||
/** Forcibly use a Label, despite passing a non-string node. */
|
||||
useLabel?: boolean;
|
||||
/** Description of the field */
|
||||
description?: React.ReactNode;
|
||||
/** Indicates if field is in invalid state */
|
||||
@@ -45,6 +47,7 @@ export const Field = React.forwardRef<HTMLDivElement, FieldProps>(
|
||||
(
|
||||
{
|
||||
label,
|
||||
useLabel,
|
||||
description,
|
||||
horizontal,
|
||||
invalid,
|
||||
@@ -63,14 +66,25 @@ export const Field = React.forwardRef<HTMLDivElement, FieldProps>(
|
||||
const styles = useStyles2(getFieldStyles);
|
||||
const inputId = htmlFor ?? getChildId(children);
|
||||
|
||||
const labelElement =
|
||||
typeof label === 'string' ? (
|
||||
let labelElement: React.ReactNode;
|
||||
if (typeof label === 'string') {
|
||||
labelElement = (
|
||||
<Label htmlFor={inputId} description={description}>
|
||||
{`${label}${required ? ' *' : ''}`}
|
||||
{label + (required ? ' *' : '')}
|
||||
</Label>
|
||||
) : (
|
||||
label
|
||||
);
|
||||
} else if (useLabel) {
|
||||
labelElement = (
|
||||
<Label htmlFor={inputId} description={description}>
|
||||
<span>
|
||||
{label}
|
||||
{required ? ' *' : ''}
|
||||
</span>
|
||||
</Label>
|
||||
);
|
||||
} else {
|
||||
labelElement = label;
|
||||
}
|
||||
|
||||
const childProps = deleteUndefinedProps({ invalid, disabled, loading });
|
||||
return (
|
||||
|
||||
@@ -62,7 +62,7 @@ func (s *ServiceImpl) getAdminNode(c *contextmodel.ReqContext) (*navtree.NavLink
|
||||
}
|
||||
if hasAccess(ac.EvalPermission(ac.ActionSettingsRead, ac.ScopeSettingsAll)) && s.features.IsEnabled(ctx, featuremgmt.FlagProvisioning) {
|
||||
generalNodeLinks = append(generalNodeLinks, &navtree.NavLink{
|
||||
Text: "Provisioning",
|
||||
Text: "Remote provisioning",
|
||||
Id: "provisioning",
|
||||
SubTitle: "Manage resources from remote repositories",
|
||||
Url: s.cfg.AppSubURL + "/admin/provisioning",
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { css } from '@emotion/css';
|
||||
|
||||
import { Box, Stack, Text, LinkButton, Icon, IconName } from '@grafana/ui';
|
||||
import { GrafanaTheme2 } from '@grafana/data';
|
||||
import { Box, Stack, Text, LinkButton, Icon, IconName, useStyles2 } from '@grafana/ui';
|
||||
|
||||
import { FeatureCard } from './FeatureCard';
|
||||
|
||||
@@ -29,46 +30,60 @@ interface EnhancedFeaturesProps {
|
||||
onSetupPublicAccess: () => void;
|
||||
}
|
||||
|
||||
export const EnhancedFeatures = ({ hasPublicAccess, hasImageRenderer, onSetupPublicAccess }: EnhancedFeaturesProps) => (
|
||||
<Box marginTop={2}>
|
||||
<Text variant="h2">Unlock enhanced functionality for GitHub</Text>
|
||||
<Box marginTop={4}>
|
||||
<Stack direction="row" gap={2}>
|
||||
<FeatureCard
|
||||
title="Instantenous provisioning"
|
||||
description="Automatically provision and update your dashboards as soon as changes are pushed to your GitHub repository"
|
||||
icon={<IconCircle icon="sync" color="primary" background="rgba(24, 121, 219, 0.12)" />}
|
||||
action={
|
||||
!hasPublicAccess && (
|
||||
<LinkButton fill="outline" onClick={onSetupPublicAccess}>
|
||||
Set up public access
|
||||
</LinkButton>
|
||||
)
|
||||
}
|
||||
showBorder
|
||||
/>
|
||||
<FeatureCard
|
||||
title="Visual previews in pull requests"
|
||||
description="Review how your changes look like before going live in Grafana and directly in pull requests"
|
||||
icon={
|
||||
<Stack direction="row" gap={2}>
|
||||
<IconCircle icon="camera" color="orange" background="rgba(255, 120, 10, 0.12)" />
|
||||
<IconCircle icon="code-branch" color="purple" background="rgba(135, 73, 237, 0.12)" />
|
||||
</Stack>
|
||||
}
|
||||
action={
|
||||
!hasImageRenderer && (
|
||||
<LinkButton
|
||||
fill="outline"
|
||||
href="https://grafana.com/grafana/plugins/grafana-image-renderer/"
|
||||
icon="external-link-alt"
|
||||
>
|
||||
Set up image rendering
|
||||
</LinkButton>
|
||||
)
|
||||
}
|
||||
/>
|
||||
</Stack>
|
||||
export const EnhancedFeatures = ({ hasPublicAccess, hasImageRenderer, onSetupPublicAccess }: EnhancedFeaturesProps) => {
|
||||
const style = useStyles2(getStyles);
|
||||
|
||||
return (
|
||||
<Box marginTop={2}>
|
||||
<Text variant="h2">Unlock enhanced functionality for GitHub</Text>
|
||||
<Box marginTop={4}>
|
||||
<Stack direction="row" gap={2}>
|
||||
<FeatureCard
|
||||
title="Instant updates and pull requests with webhooks"
|
||||
description="Get instant updates in Grafana as soon as changes are committed. Review and approve changes using pull requests before they go live."
|
||||
icon={<IconCircle icon="sync" color="primary" background="rgba(24, 121, 219, 0.12)" />}
|
||||
action={
|
||||
!hasPublicAccess && (
|
||||
<LinkButton fill="outline" onClick={onSetupPublicAccess}>
|
||||
Set up public access
|
||||
</LinkButton>
|
||||
)
|
||||
}
|
||||
/>
|
||||
|
||||
<div className={style.separator} />
|
||||
|
||||
<FeatureCard
|
||||
title="Visual previews in pull requests"
|
||||
description="See visual previews of dashboard updates directly in pull requests"
|
||||
icon={
|
||||
<Stack direction="row" gap={2}>
|
||||
<IconCircle icon="camera" color="orange" background="rgba(255, 120, 10, 0.12)" />
|
||||
<IconCircle icon="code-branch" color="purple" background="rgba(135, 73, 237, 0.12)" />
|
||||
</Stack>
|
||||
}
|
||||
action={
|
||||
!hasImageRenderer && (
|
||||
<LinkButton
|
||||
fill="outline"
|
||||
href="https://grafana.com/grafana/plugins/grafana-image-renderer/"
|
||||
icon="external-link-alt"
|
||||
>
|
||||
Set up image rendering
|
||||
</LinkButton>
|
||||
)
|
||||
}
|
||||
/>
|
||||
</Stack>
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
);
|
||||
};
|
||||
|
||||
function getStyles(theme: GrafanaTheme2) {
|
||||
return {
|
||||
separator: css({
|
||||
borderRight: `2px solid ${theme.colors.border.weak}`,
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
import { css } from '@emotion/css';
|
||||
|
||||
import { Box, Stack, Text } from '@grafana/ui';
|
||||
|
||||
interface FeatureCardProps {
|
||||
@@ -7,17 +5,11 @@ interface FeatureCardProps {
|
||||
description: string;
|
||||
icon?: React.ReactNode;
|
||||
action?: React.ReactNode;
|
||||
showBorder?: boolean;
|
||||
}
|
||||
|
||||
export const FeatureCard = ({ title, description, icon, action, showBorder = false }: FeatureCardProps) => (
|
||||
export const FeatureCard = ({ title, description, icon, action }: FeatureCardProps) => (
|
||||
<Box width="25%" padding={2}>
|
||||
<div
|
||||
className={css({
|
||||
borderRight: showBorder ? ': 1px solid rgba(204, 204, 220, 0.15);' : undefined,
|
||||
height: `100%`,
|
||||
})}
|
||||
>
|
||||
<div style={{ height: '100%' }}>
|
||||
<Stack direction="column" gap={2}>
|
||||
{icon}
|
||||
<Text variant="h3">{title}</Text>
|
||||
|
||||
@@ -4,14 +4,18 @@ import { Repository } from 'app/api/clients/provisioning';
|
||||
import { ConnectRepositoryButton } from '../Shared/ConnectRepositoryButton';
|
||||
|
||||
interface FeatureItemProps {
|
||||
children: React.ReactNode;
|
||||
children: NonNullable<React.ReactNode>;
|
||||
}
|
||||
|
||||
const FeatureItem = ({ children }: FeatureItemProps) => (
|
||||
<Text variant="body">
|
||||
<Icon name="check" className="text-success" /> {children}
|
||||
</Text>
|
||||
);
|
||||
const FeatureItem = ({ children }: FeatureItemProps) => {
|
||||
// We use a stack here to ensure the icon and text are aligned correctly.
|
||||
return (
|
||||
<Stack direction="row" gap={1}>
|
||||
<Icon name="check" className="text-success" />
|
||||
<Text variant="body">{children}</Text>
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
interface FeaturesListProps {
|
||||
repos?: Repository[];
|
||||
@@ -48,17 +52,12 @@ export const FeaturesList = ({
|
||||
|
||||
return (
|
||||
<Stack direction="column" gap={2}>
|
||||
<Text variant="h2">Provisioning as-code directly from Grafana</Text>
|
||||
<Text variant="h2">Manage your dashboards with remote provisioning</Text>
|
||||
<FeatureItem>Manage dashboards as code and provision updates automatically</FeatureItem>
|
||||
<FeatureItem>
|
||||
Manage your dashboards as code and deploy them automatically from your GitHub repository or local storage
|
||||
</FeatureItem>
|
||||
<FeatureItem>
|
||||
Review, discuss, and approve dashboard changes with your team before they go live using GitHub pull requests
|
||||
</FeatureItem>
|
||||
<FeatureItem>
|
||||
Export your existing dashboards as code and store them in GitHub repositories for version control and
|
||||
collaboration
|
||||
Store dashboards in version-controlled storage for better organization and history tracking
|
||||
</FeatureItem>
|
||||
<FeatureItem>Migrate existing dashboards to storage for provisioning</FeatureItem>
|
||||
{hasPublicAccess && (
|
||||
<FeatureItem>
|
||||
Automatically provision and update your dashboards as soon as changes are pushed to your GitHub repository
|
||||
@@ -68,9 +67,12 @@ export const FeaturesList = ({
|
||||
<FeatureItem>Visual previews in pull requests to review your changes before going live</FeatureItem>
|
||||
)}
|
||||
|
||||
<LinkButton fill="text" href="#" icon="external-link-alt">
|
||||
Learn more
|
||||
</LinkButton>
|
||||
{false && (
|
||||
// We haven't gotten the design for this quite yet.
|
||||
<LinkButton fill="text" href="#" icon="external-link-alt">
|
||||
Learn more
|
||||
</LinkButton>
|
||||
)}
|
||||
|
||||
{actions()}
|
||||
</Stack>
|
||||
|
||||
@@ -11,7 +11,7 @@ export default function GettingStartedPage({ items }: Props) {
|
||||
<Page
|
||||
navId="provisioning"
|
||||
pageNav={{
|
||||
text: 'Getting started with Provisioning',
|
||||
text: 'Remote provisioning',
|
||||
subTitle:
|
||||
'Provisioning is a feature that allows you to manage your dashboards using GitHub and other storage systems',
|
||||
}}
|
||||
|
||||
@@ -24,7 +24,7 @@ export function ConnectRepositoryButton({ items }: Props) {
|
||||
disabled={true}
|
||||
tooltip={`Max repositories already created (${state.repoCount})`}
|
||||
>
|
||||
Maximum repos exist ({state.repoCount})
|
||||
Maximum repositories exist ({state.repoCount})
|
||||
</LinkButton>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,36 +1,69 @@
|
||||
import { ControlledCollapse, TextLink } from '@grafana/ui';
|
||||
import { css } from '@emotion/css';
|
||||
|
||||
import { GrafanaTheme2 } from '@grafana/data';
|
||||
import { TextLink, useStyles2 } from '@grafana/ui';
|
||||
|
||||
export function TokenPermissionsInfo() {
|
||||
const styles = useStyles2(getStyles);
|
||||
|
||||
return (
|
||||
<ControlledCollapse collapsible label="Access Token Permissions">
|
||||
<div className={styles.container}>
|
||||
<div>
|
||||
To create a new access token, navigate to{' '}
|
||||
Go to{' '}
|
||||
<TextLink external href="https://github.com/settings/personal-access-tokens/new">
|
||||
Personal access tokens
|
||||
</TextLink>{' '}
|
||||
GitHub Personal Access Tokens
|
||||
</TextLink>
|
||||
. Make sure to include these permissions under <b>Repository</b>:
|
||||
</div>
|
||||
|
||||
<p>Select the appropriate owner and repository. Then expand repository permissions, granting</p>
|
||||
<table style={{ fontSize: '2em' }}>
|
||||
<table className={styles.permissionTable}>
|
||||
<tbody>
|
||||
<tr className={styles.headerSeparator}>
|
||||
<th>Permission</th>
|
||||
<th>Access</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Content</td>
|
||||
<td>Contents</td>
|
||||
<td>Read and write</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Metadata</td>
|
||||
<td>Read only</td>
|
||||
<td>Read-only</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Pull requests </td>
|
||||
<td>Pull requests</td>
|
||||
<td>Read and write</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Webhooks</td>
|
||||
<td>Read and write </td>
|
||||
<td>Read and write</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</ControlledCollapse>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function getStyles(theme: GrafanaTheme2) {
|
||||
return {
|
||||
container: css({
|
||||
marginBottom: theme.spacing(1),
|
||||
backgroundColor: theme.colors.background.secondary,
|
||||
border: `1px solid ${theme.colors.border.weak}`,
|
||||
position: 'relative',
|
||||
borderRadius: theme.shape.radius.default,
|
||||
width: '100%',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
flex: '1 1 0',
|
||||
padding: theme.spacing(theme.components.panel.padding),
|
||||
}),
|
||||
permissionTable: css({
|
||||
tableLayout: 'auto',
|
||||
width: '40%',
|
||||
}),
|
||||
headerSeparator: css({
|
||||
borderBottom: `1px solid ${theme.colors.border.weak}`,
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -132,7 +132,7 @@ export function BootstrapStep({ onOptionSelect, settingsData, repoName }: Props)
|
||||
<>
|
||||
{Boolean(state.resourceCount) && (
|
||||
<Alert severity="info" title="Note">
|
||||
Dashboards will be unavailable while running this process.
|
||||
Dashboards will be unavailable while running this process
|
||||
</Alert>
|
||||
)}
|
||||
{Boolean(state.fileCount) && Boolean(state.resourceCount) && (
|
||||
|
||||
@@ -6,7 +6,10 @@ export default function ConnectPage() {
|
||||
return (
|
||||
<Page
|
||||
navId="provisioning"
|
||||
pageNav={{ text: 'Connect to repository', subTitle: 'Connect to a repository to manage your resources' }}
|
||||
pageNav={{
|
||||
text: 'Connect to external storage',
|
||||
subTitle: 'Connect to an external storage to manage your resources',
|
||||
}}
|
||||
>
|
||||
<Page.Contents>
|
||||
<ProvisioningWizard />
|
||||
|
||||
@@ -29,25 +29,28 @@ export function ConnectStep() {
|
||||
|
||||
return (
|
||||
<Stack direction="column">
|
||||
<Combobox
|
||||
options={typeOptions}
|
||||
value={type}
|
||||
onChange={(value) => {
|
||||
const repoType = value?.value;
|
||||
setValue('repository.type', repoType);
|
||||
setValue(
|
||||
'repository.workflows',
|
||||
getWorkflowOptions(repoType).map((v) => v.value)
|
||||
);
|
||||
}}
|
||||
/>
|
||||
<Field label="Storage type" required description="Choose the type of storage for your resources">
|
||||
<Combobox
|
||||
options={typeOptions}
|
||||
value={type}
|
||||
onChange={(value) => {
|
||||
const repoType = value?.value;
|
||||
setValue('repository.type', repoType);
|
||||
setValue(
|
||||
'repository.workflows',
|
||||
getWorkflowOptions(repoType).map((v) => v.value)
|
||||
);
|
||||
}}
|
||||
/>
|
||||
</Field>
|
||||
|
||||
{isGithub && (
|
||||
<>
|
||||
<TokenPermissionsInfo />
|
||||
<Field
|
||||
label={'Token'}
|
||||
label={'Enter your access token'}
|
||||
required
|
||||
description="Paste your GitHub personal access token"
|
||||
error={errors.repository?.token?.message}
|
||||
invalid={!!errors.repository?.token}
|
||||
>
|
||||
@@ -60,7 +63,7 @@ export function ConnectStep() {
|
||||
<SecretInput
|
||||
{...field}
|
||||
id={'token'}
|
||||
placeholder={'ghp_yourTokenHere1234567890abcdEFGHijklMNOP'}
|
||||
placeholder={'github_pat_yourTokenHere1234567890abcdEFGHijklMNOP'}
|
||||
isConfigured={tokenConfigured}
|
||||
onReset={() => {
|
||||
setValue('repository.token', '');
|
||||
@@ -73,21 +76,22 @@ export function ConnectStep() {
|
||||
</Field>
|
||||
|
||||
<Field
|
||||
label={'Repository URL'}
|
||||
label={'Enter your Repository URL'}
|
||||
error={errors.repository?.url?.message}
|
||||
invalid={!!errors.repository?.url}
|
||||
description={'Enter the GitHub repository URL'}
|
||||
description={'Paste the URL of your GitHub repository'}
|
||||
required
|
||||
>
|
||||
<Input
|
||||
{...register('repository.url', {
|
||||
required: 'This field is required.',
|
||||
pattern: {
|
||||
// TODO: The regex is not correct when we support GHES.
|
||||
value: /^(?:https:\/\/github\.com\/)?[^/]+\/[^/]+$/,
|
||||
message: 'Please enter a valid GitHub repository URL',
|
||||
},
|
||||
})}
|
||||
placeholder={'https://github.com/username/repo-name'}
|
||||
placeholder={'https://github.com/username/repo'}
|
||||
/>
|
||||
</Field>
|
||||
|
||||
|
||||
@@ -1,22 +1,18 @@
|
||||
import { css } from '@emotion/css';
|
||||
import { useEffect } from 'react';
|
||||
import { Controller, useFormContext } from 'react-hook-form';
|
||||
import { useNavigate } from 'react-router-dom-v5-compat';
|
||||
|
||||
import { Alert, Field, FieldSet, Input, MultiCombobox, Stack, Switch, Text } from '@grafana/ui';
|
||||
import { GrafanaTheme2 } from '@grafana/data';
|
||||
import { Field, Input, MultiCombobox, Stack, Switch, useStyles2 } from '@grafana/ui';
|
||||
|
||||
import { getWorkflowOptions } from '../Config/ConfigForm';
|
||||
import { checkPublicAccess, checkImageRenderer } from '../GettingStarted/features';
|
||||
import { GETTING_STARTED_URL } from '../constants';
|
||||
|
||||
import { WizardFormData } from './types';
|
||||
|
||||
export function FinishStep() {
|
||||
const {
|
||||
register,
|
||||
watch,
|
||||
control,
|
||||
formState: { errors },
|
||||
} = useFormContext<WizardFormData>();
|
||||
const { register, watch, control, formState } = useFormContext<WizardFormData>();
|
||||
const { errors } = formState;
|
||||
|
||||
const type = watch('repository.type');
|
||||
const isGithub = type === 'github';
|
||||
@@ -24,7 +20,18 @@ export function FinishStep() {
|
||||
const hasImageRenderer = checkImageRenderer();
|
||||
// Enable sync by default
|
||||
const { setValue } = useFormContext<WizardFormData>();
|
||||
const navigate = useNavigate();
|
||||
const style = useStyles2(getStyles);
|
||||
|
||||
if (!isPublic || !hasImageRenderer) {
|
||||
if (formState.defaultValues?.repository) {
|
||||
formState.defaultValues.repository.generateDashboardPreviews = false;
|
||||
}
|
||||
}
|
||||
if (!isPublic) {
|
||||
if (formState.defaultValues?.repository) {
|
||||
// TODO: Disable webhooks by default
|
||||
}
|
||||
}
|
||||
|
||||
// Set sync enabled by default
|
||||
useEffect(() => {
|
||||
@@ -33,107 +40,89 @@ export function FinishStep() {
|
||||
|
||||
return (
|
||||
<Stack direction="column">
|
||||
<FieldSet label="Automatic pulling">
|
||||
{isGithub && isPublic && (
|
||||
<Stack>
|
||||
<Alert severity="info" title="Instantenous provisioning available">
|
||||
Automatically provision and update your dashboards as soon as changes are pushed to your GitHub
|
||||
repository.
|
||||
</Alert>
|
||||
</Stack>
|
||||
)}
|
||||
{isGithub && !isPublic && (
|
||||
<Alert
|
||||
title={'Public URL not configured'}
|
||||
severity="info"
|
||||
buttonContent={<span>Instructions</span>}
|
||||
onRemove={() => navigate(GETTING_STARTED_URL)}
|
||||
>
|
||||
Changes in git will eventually be pulled depending on the synchronization interval.
|
||||
</Alert>
|
||||
)}
|
||||
<Field label={'Interval (seconds)'}>
|
||||
{isGithub && (
|
||||
<Field
|
||||
label="Update instance interval (seconds)"
|
||||
description="How often shall the instance pull updates from GitHub?"
|
||||
required
|
||||
>
|
||||
<Input
|
||||
{...register('repository.sync.intervalSeconds', { valueAsNumber: true })}
|
||||
type={'number'}
|
||||
placeholder={'60'}
|
||||
type="number"
|
||||
placeholder="60"
|
||||
/>
|
||||
</Field>
|
||||
</FieldSet>
|
||||
<FieldSet label="Collaboration">
|
||||
)}
|
||||
|
||||
<Field
|
||||
label="Workflows"
|
||||
description="Select the workflows that are allowed within this repository"
|
||||
required
|
||||
error={errors.repository?.workflows?.message}
|
||||
invalid={!!errors.repository?.workflows}
|
||||
>
|
||||
<Controller
|
||||
name="repository.workflows"
|
||||
control={control}
|
||||
rules={{ required: 'This field is required.' }}
|
||||
render={({ field: { ref, onChange, ...field } }) => (
|
||||
<MultiCombobox
|
||||
options={getWorkflowOptions(type)}
|
||||
placeholder="Read-only repository"
|
||||
onChange={(val) => {
|
||||
onChange(val.map((v) => v.value));
|
||||
}}
|
||||
{...field}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</Field>
|
||||
|
||||
{isGithub && false /* TODO */ && (
|
||||
<Field
|
||||
label={'Workflows'}
|
||||
required
|
||||
error={errors.repository?.workflows?.message}
|
||||
invalid={!!errors.repository?.workflows}
|
||||
label={'Enable webhooks on changes' /* TODO: Link to docs when !isPublic */}
|
||||
description="Enable webhooks to automatically notify Grafana when a change occurs in the repository. This will allow Grafana to pull changes as soon as they are made."
|
||||
disabled={!isPublic}
|
||||
>
|
||||
<Controller
|
||||
name={'repository.workflows'}
|
||||
control={control}
|
||||
rules={{ required: 'This field is required.' }}
|
||||
render={({ field: { ref, onChange, ...field } }) => {
|
||||
return (
|
||||
<MultiCombobox
|
||||
options={getWorkflowOptions(type)}
|
||||
placeholder={'Readonly repository'}
|
||||
onChange={(val) => {
|
||||
onChange(val.map((v) => v.value));
|
||||
}}
|
||||
{...field}
|
||||
/>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
{/* TODO: Make an option for the switch to control */}
|
||||
<Switch id="repository.webhook.enable" />
|
||||
</Field>
|
||||
{isGithub && (
|
||||
<>
|
||||
{isPublic ? (
|
||||
<Alert severity="info" title="Preview links available">
|
||||
Preview links will be automatically added to pull requests when changes are made.
|
||||
</Alert>
|
||||
) : (
|
||||
<Alert
|
||||
severity="info"
|
||||
title="Public URL not configured"
|
||||
onRemove={() => navigate(GETTING_STARTED_URL)}
|
||||
buttonContent={<span>Instructions</span>}
|
||||
>
|
||||
Preview links in pull requests will not be available until a public URL is configured.
|
||||
</Alert>
|
||||
)}
|
||||
)}
|
||||
|
||||
{!hasImageRenderer && (
|
||||
<Alert
|
||||
severity="info"
|
||||
title="Image renderer not configured"
|
||||
onRemove={() => navigate(GETTING_STARTED_URL)}
|
||||
buttonContent={<span>Instructions</span>}
|
||||
>
|
||||
The image renderer is not configured. Preview images will not be available.
|
||||
</Alert>
|
||||
)}
|
||||
|
||||
{hasImageRenderer && isPublic && (
|
||||
<>
|
||||
<Field
|
||||
label={'Attach dashboard previews to pull requests'}
|
||||
description={
|
||||
<Text element="span">Render before/after images and link them to the pull request.</Text>
|
||||
}
|
||||
>
|
||||
<Switch
|
||||
{...register('repository.generateDashboardPreviews')}
|
||||
id={'repository.generateDashboardPreviews'}
|
||||
/>
|
||||
</Field>
|
||||
<Alert severity="info" title="Note">
|
||||
This will render dashboards into an image that can be access by a public URL
|
||||
</Alert>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</FieldSet>
|
||||
{isGithub && (
|
||||
<Field
|
||||
useLabel
|
||||
label={
|
||||
<span>
|
||||
Enable dashboard previews in pull requests{' '}
|
||||
<span className={style.explanation}>
|
||||
(Requires image rendering.{' '}
|
||||
<a className={style.explanationLink} href="https://grafana.com">
|
||||
Set up image rendering
|
||||
</a>
|
||||
)
|
||||
</span>
|
||||
</span>
|
||||
}
|
||||
description="Adds an image preview of dashboard changes in pull requests. Images of your Grafana dashboards will be shared in your Git repository and visible to anyone with repository access."
|
||||
disabled={!hasImageRenderer || !isPublic}
|
||||
>
|
||||
<Switch {...register('repository.generateDashboardPreviews')} id="repository.generateDashboardPreviews" />
|
||||
</Field>
|
||||
)}
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
|
||||
function getStyles(theme: GrafanaTheme2) {
|
||||
return {
|
||||
explanation: css({
|
||||
color: theme.colors.text.disabled,
|
||||
fontStyle: 'italic',
|
||||
}),
|
||||
explanationLink: css({
|
||||
color: theme.colors.text.link,
|
||||
fontStyle: 'italic',
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ import { WizardContent } from './WizardContent';
|
||||
import { WizardFormData, WizardStep } from './types';
|
||||
|
||||
const steps: Array<Step<WizardStep>> = [
|
||||
{ id: 'connection', name: 'Connect', title: 'Connect to repository', submitOnNext: true },
|
||||
{ id: 'connection', name: 'Connect', title: 'Connect to external storage', submitOnNext: true },
|
||||
{ id: 'bootstrap', name: 'Bootstrap', title: 'Bootstrap repository', submitOnNext: true },
|
||||
{ id: 'migrate', name: 'Resources', title: 'Migrate resources', submitOnNext: false },
|
||||
{ id: 'pull', name: 'Resources', title: 'Pull resources', submitOnNext: false },
|
||||
|
||||
@@ -10,21 +10,21 @@ const migrateInstance: ModeOption = {
|
||||
target: 'instance',
|
||||
operation: 'migrate',
|
||||
label: 'Migrate instance to repository',
|
||||
description: 'Save all Grafana resources to repository',
|
||||
description: 'Save all Grafana resources in the repository',
|
||||
};
|
||||
|
||||
const pullInstance: ModeOption = {
|
||||
target: 'instance',
|
||||
operation: 'pull',
|
||||
label: 'Pull from repository to instance',
|
||||
description: 'Pull resources from repository into this Grafana instance',
|
||||
description: 'Pull resources from the repository into this Grafana instance',
|
||||
};
|
||||
|
||||
const pullFolder: ModeOption = {
|
||||
target: 'folder',
|
||||
operation: 'pull',
|
||||
label: 'Pull from repository to folder',
|
||||
description: 'Pull repository resources into a specific folder',
|
||||
description: 'Pull repository resources into a repository-managed Grafana folder',
|
||||
};
|
||||
|
||||
function getDisabledReason(action: ModeOption, resourceCount: number, folderConnected?: boolean) {
|
||||
@@ -80,7 +80,7 @@ export function getState(
|
||||
|
||||
const state: SystemState = {
|
||||
resourceCount,
|
||||
resourceCountString: counts.join(','),
|
||||
resourceCountString: counts.join(',\n'),
|
||||
fileCount,
|
||||
actions: [],
|
||||
disabled: [],
|
||||
|
||||
Reference in New Issue
Block a user