Admin: Add support bundles (#60536)

* Add support bundles

Co-authored-by: ievaVasiljeva <ieva.vasiljeva@grafana.com>
Co-authored-by: Kalle Persson <kalle.persson@grafana.com>

* tweak code owners

* rename and lint frontend

* lint

* fix backend lint

* register feature flag

* add feature toggle. fix small backend issues

Co-authored-by: ievaVasiljeva <ieva.vasiljeva@grafana.com>
Co-authored-by: Kalle Persson <kalle.persson@grafana.com>
This commit is contained in:
Jo
2022-12-20 11:13:37 +01:00
committed by GitHub
co-authored by ievaVasiljeva Kalle Persson
parent 6e2b148745
commit 2c7410c87d
24 changed files with 1174 additions and 4 deletions
@@ -0,0 +1,73 @@
import React, { useEffect } from 'react';
import { useAsyncFn } from 'react-use';
import { dateTimeFormat } from '@grafana/data';
import { getBackendSrv } from '@grafana/runtime';
import { LinkButton } from '@grafana/ui';
import { Page } from 'app/core/components/Page/Page';
const subTitle = (
<span>
Support bundles allow you to easily collect and share Grafana logs, configuration, and data with the Grafana Labs
team.
</span>
);
type SupportBundleState = 'complete' | 'error' | 'timeout' | 'pending';
interface SupportBundle {
uid: string;
state: SupportBundleState;
creator: string;
createdAt: number;
expiresAt: number;
}
const getBundles = () => {
return getBackendSrv().get<SupportBundle[]>('/api/support-bundles');
};
function SupportBundles() {
const [bundlesState, fetchBundles] = useAsyncFn(getBundles, []);
useEffect(() => {
fetchBundles();
}, [fetchBundles]);
return (
<Page navId="support-bundles" subTitle={subTitle}>
<Page.Contents isLoading={bundlesState.loading}>
<LinkButton href="admin/support-bundles/create" variant="primary">
Create New Support Bundle
</LinkButton>
<table className="filter-table form-inline">
<thead>
<tr>
<th>Date</th>
<th>Requested by</th>
<th>Expires</th>
<th style={{ width: '1%' }} />
</tr>
</thead>
<tbody>
{bundlesState?.value?.map((b) => (
<tr key={b.uid}>
<th>{dateTimeFormat(b.createdAt * 1000)}</th>
<th>{b.creator}</th>
<th>{dateTimeFormat(b.expiresAt * 1000)}</th>
<th>
<LinkButton disabled={b.state !== 'complete'} target={'_self'} href={'/api/support-bundles/' + b.uid}>
Download
</LinkButton>
</th>
</tr>
))}
</tbody>
</table>
</Page.Contents>
</Page>
);
}
export default SupportBundles;
@@ -0,0 +1,96 @@
import React, { useCallback, useEffect, useState } from 'react';
import { useAsyncFn } from 'react-use';
import { getBackendSrv, locationService } from '@grafana/runtime';
import { Form, Button, Field, Checkbox } from '@grafana/ui';
import { Page } from 'app/core/components/Page/Page';
// move to types
export interface SupportBundleCreateRequest {
collectors: string[];
}
export interface SupportBundleCollector {
uid: string;
displayName: string;
description: string;
includedByDefault: boolean;
default: boolean;
}
export interface Props {}
const createSupportBundle = async (data: SupportBundleCreateRequest) => {
const result = await getBackendSrv().post('/api/support-bundles', data);
return result;
};
export const SupportBundlesCreate = ({}: Props): JSX.Element => {
const onSubmit = useCallback(async (data) => {
try {
const selectedLabelsArray = Object.keys(data).filter((key) => data[key]);
const response = await createSupportBundle({ collectors: selectedLabelsArray });
console.info(response);
} catch (e) {
console.error(e);
}
locationService.push('/admin/support-bundles');
}, []);
const [components, setComponents] = useState<SupportBundleCollector[]>([]);
// populate components from the backend
const populateComponents = async () => {
return await getBackendSrv().get('/api/support-bundles/collectors');
};
const [state, fetchComponents] = useAsyncFn(populateComponents);
useEffect(() => {
fetchComponents().then((res) => {
setComponents(res);
});
}, [fetchComponents]);
// turn components into a uuid -> enabled map
const values: Record<string, boolean> = components.reduce((acc, curr) => {
return { ...acc, [curr.uid]: curr.default };
}, {});
return (
<Page navId="support-bundles" pageNav={{ text: 'Create support bundle' }}>
<Page.Contents>
<Page.OldNavOnly>
<h3 className="page-sub-heading">Create support bundle</h3>
</Page.OldNavOnly>
{state.error && <p>{state.error}</p>}
{!!components.length && (
<Form defaultValues={values} onSubmit={onSubmit} validateOn="onSubmit">
{({ register, errors }) => {
return (
<>
{components.map((component) => {
return (
<Field key={component.uid}>
<Checkbox
{...register(component.uid)}
label={component.displayName}
id={component.uid}
description={component.description}
defaultChecked={component.default}
disabled={component.includedByDefault}
/>
</Field>
);
})}
<Button type="submit">Create</Button>
</>
);
}}
</Form>
)}
</Page.Contents>
</Page>
);
};
export default SupportBundlesCreate;