POC/Provisioning: Move the save UI to its own file (#96966)
Co-authored-by: Clarity-89 <homes89@ukr.net>
This commit is contained in:
1 parent
96e5ef149c
commit
0527d0ccbe
5 files changed
+164
-45
No files matched your search
@@ -44,6 +44,7 @@ func FallbackResourceLoader(data []byte) (*unstructured.Unstructured, *schema.Gr
|
||||
return &unstructured.Unstructured{
|
||||
Object: map[string]interface{}{
|
||||
"apiVersion": gvk.GroupVersion().String(),
|
||||
"kind": "Dashboard",
|
||||
"metadata": map[string]any{
|
||||
"name": value["uid"],
|
||||
},
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { getBackendSrv } from '@grafana/runtime';
|
||||
import { SceneComponentProps, SceneObjectBase, SceneObjectState, SceneObjectRef } from '@grafana/scenes';
|
||||
import { Button, Drawer, Tab, TabsBar } from '@grafana/ui';
|
||||
import { Drawer, Tab, TabsBar } from '@grafana/ui';
|
||||
import { AnnoKeyRepoName } from 'app/features/apiserver/types';
|
||||
import { SaveDashboardDiff } from 'app/features/dashboard/components/SaveDashboard/SaveDashboardDiff';
|
||||
|
||||
@@ -8,6 +7,7 @@ import { DashboardScene } from '../scene/DashboardScene';
|
||||
|
||||
import { SaveDashboardAsForm } from './SaveDashboardAsForm';
|
||||
import { SaveDashboardForm } from './SaveDashboardForm';
|
||||
import { SaveProvisionedDashboard } from './SaveProvisionedDashboard';
|
||||
import { SaveProvisionedDashboardForm } from './SaveProvisionedDashboardForm';
|
||||
import { getDashboardChangesFromScene } from './getDashboardChangesFromScene';
|
||||
|
||||
@@ -50,7 +50,10 @@ export class SaveDashboardDrawer extends SceneObjectBase<SaveDashboardDrawerStat
|
||||
const changesCount = diffCount + (hasFolderChanges ? 1 : 0);
|
||||
const dashboard = model.state.dashboardRef.resolve();
|
||||
const { meta } = dashboard.useState();
|
||||
const { provisioned: isProvisioned, folderTitle, provisioning } = meta;
|
||||
const { provisioned: isProvisioned, folderTitle } = meta;
|
||||
|
||||
// Provisioned dashboards have k8s metadata annotations
|
||||
const isProvisionedNG = meta.k8s?.annotations?.[AnnoKeyRepoName]
|
||||
|
||||
const tabs = (
|
||||
<TabsBar>
|
||||
@@ -69,7 +72,7 @@ export class SaveDashboardDrawer extends SceneObjectBase<SaveDashboardDrawerStat
|
||||
let title = 'Save dashboard';
|
||||
if (saveAsCopy) {
|
||||
title = 'Save dashboard copy';
|
||||
} else if (isProvisioned) {
|
||||
} else if (isProvisioned || isProvisionedNG) {
|
||||
title = 'Provisioned dashboard';
|
||||
}
|
||||
|
||||
@@ -87,41 +90,8 @@ export class SaveDashboardDrawer extends SceneObjectBase<SaveDashboardDrawerStat
|
||||
);
|
||||
}
|
||||
|
||||
// Preview (eg, NOT saved in grafana database)
|
||||
if (provisioning) {
|
||||
return (
|
||||
<div>
|
||||
<h1>Loaded from external repository</h1>
|
||||
<h3>{provisioning.repo}</h3>
|
||||
<a href={provisioning.file}>{provisioning.file}</a>
|
||||
<div>
|
||||
<Button
|
||||
onClick={() => {
|
||||
getBackendSrv()
|
||||
.put(provisioning.file, changedSaveModel, {
|
||||
params: provisioning.ref ? { ref: provisioning.ref } : undefined,
|
||||
})
|
||||
.then((v) => {
|
||||
console.log('WROTE', v);
|
||||
alert('WROTE value');
|
||||
});
|
||||
}}
|
||||
>
|
||||
SAVE
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// Saved in grafana database, BUT must write to a remote repo for edit
|
||||
if (meta.k8s?.annotations?.[AnnoKeyRepoName]) {
|
||||
return (
|
||||
<div>
|
||||
<h1>Saved from external repository</h1>
|
||||
<pre>{JSON.stringify(meta.k8s.annotations, null, ' ')}</pre>
|
||||
</div>
|
||||
);
|
||||
if (isProvisionedNG) {
|
||||
return <SaveProvisionedDashboard meta={meta} dashboard={dashboard} changeInfo={changeInfo} drawer={model} />;
|
||||
}
|
||||
|
||||
if (saveAsCopy || changeInfo.isNew) {
|
||||
|
||||
@@ -0,0 +1,134 @@
|
||||
import { css } from '@emotion/css';
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
|
||||
import { config, getBackendSrv } from '@grafana/runtime';
|
||||
import { Button, Stack, Box, TextArea, Field, Input, Alert } from '@grafana/ui';
|
||||
import { AnnoKeyRepoName, AnnoKeyRepoPath } from 'app/features/apiserver/types';
|
||||
import { DashboardMeta } from 'app/types';
|
||||
|
||||
import { DashboardScene } from '../scene/DashboardScene';
|
||||
|
||||
import { SaveDashboardDrawer } from './SaveDashboardDrawer';
|
||||
import { SaveDashboardFormCommonOptions } from './SaveDashboardForm';
|
||||
import { DashboardChangeInfo } from './shared';
|
||||
|
||||
export interface Props {
|
||||
meta: DashboardMeta;
|
||||
dashboard: DashboardScene;
|
||||
drawer: SaveDashboardDrawer;
|
||||
changeInfo: DashboardChangeInfo;
|
||||
}
|
||||
|
||||
export function SaveProvisionedDashboard({ meta, drawer, changeInfo }: Props) {
|
||||
const dashboardJSON = useMemo(() => JSON.stringify(changeInfo.changedSaveModel, null, 2), [changeInfo]);
|
||||
|
||||
const [repo, setRepo] = useState<string>();
|
||||
const [path, setPath] = useState<string>();
|
||||
const [ref, setRef] = useState<string>();
|
||||
const [comment, setComment] = useState<string>();
|
||||
|
||||
useEffect(() => {
|
||||
const anno = meta.k8s?.annotations;
|
||||
if (!anno) {
|
||||
setRepo('');
|
||||
setPath('');
|
||||
setRef('');
|
||||
return;
|
||||
}
|
||||
let ref = '';
|
||||
let path = anno[AnnoKeyRepoPath] ?? '';
|
||||
const idx = path.indexOf('#');
|
||||
if (idx > 0) {
|
||||
ref = path.substring(idx + 1);
|
||||
path = path.substring(0, idx);
|
||||
}
|
||||
setRepo(anno[AnnoKeyRepoName]);
|
||||
setPath(path);
|
||||
setRef(ref);
|
||||
}, [meta]);
|
||||
|
||||
const doSave = () => {
|
||||
const url = `apis/provisioning.grafana.app/v0alpha1/namespaces/${config.namespace}/repositories/${repo}/files/${path}`;
|
||||
const params: Record<string, string> = {};
|
||||
if (ref) {
|
||||
params['ref'] = ref;
|
||||
}
|
||||
if (comment) {
|
||||
params['comment'] = comment;
|
||||
}
|
||||
|
||||
getBackendSrv()
|
||||
.put(url, dashboardJSON, { params })
|
||||
.then((v) => {
|
||||
console.log('WROTE', v);
|
||||
alert('WROTE value');
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
<Stack direction="column" gap={2} grow={1}>
|
||||
<div>
|
||||
<Alert severity="warning" title="Development feature">
|
||||
More warnings here... mostly exploratory interfaces.
|
||||
</Alert>
|
||||
</div>
|
||||
|
||||
<SaveDashboardFormCommonOptions drawer={drawer} changeInfo={changeInfo} />
|
||||
|
||||
<Field label="Repository">
|
||||
<div>{repo}</div>
|
||||
</Field>
|
||||
|
||||
<Field label="Path" description="File path inside the repository. This must be .json or .yaml">
|
||||
<Input
|
||||
value={path}
|
||||
onChange={(e) => {
|
||||
setPath(e.currentTarget.value);
|
||||
}}
|
||||
/>
|
||||
</Field>
|
||||
|
||||
<Field label="Branch" description="only supported by github right now">
|
||||
<Input
|
||||
value={ref}
|
||||
onChange={(e) => {
|
||||
setRef(e.currentTarget.value);
|
||||
}}
|
||||
/>
|
||||
</Field>
|
||||
|
||||
<Field label="Comment">
|
||||
<TextArea
|
||||
aria-label="comment"
|
||||
value={comment ?? ''}
|
||||
onChange={(e) => {
|
||||
setComment(e.currentTarget.value);
|
||||
}}
|
||||
placeholder="Add a note to describe your changes (optional)."
|
||||
autoFocus
|
||||
rows={5}
|
||||
/>
|
||||
</Field>
|
||||
|
||||
<Box paddingTop={2}>
|
||||
<Stack gap={2}>
|
||||
<Button variant="primary" onClick={doSave}>
|
||||
Save
|
||||
</Button>
|
||||
<Button variant="secondary" onClick={drawer.onClose} fill="outline">
|
||||
Cancel
|
||||
</Button>
|
||||
</Stack>
|
||||
</Box>
|
||||
</Stack>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const styles = {
|
||||
container: css({
|
||||
height: '100%',
|
||||
display: 'flex',
|
||||
}),
|
||||
};
|
||||
@@ -31,10 +31,12 @@ function createBackendSrvBaseQuery({ baseURL }: { baseURL: string }): BaseQueryF
|
||||
return backendSrvBaseQuery;
|
||||
}
|
||||
|
||||
export const BASE_URL = `apis/provisioning.grafana.app/v0alpha1/namespaces/${config.namespace}`;
|
||||
|
||||
export const baseAPI = createApi({
|
||||
reducerPath: 'provisioningAPI',
|
||||
baseQuery: createBackendSrvBaseQuery({
|
||||
baseURL: `/apis/provisioning.grafana.app/v0alpha1/namespaces/${config.namespace}`,
|
||||
baseURL: BASE_URL,
|
||||
}),
|
||||
endpoints: () => ({}),
|
||||
tagTypes: ['RepositoryList'],
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { config, getBackendSrv } from '@grafana/runtime';
|
||||
import { DashboardDataDTO, DashboardDTO } from 'app/types';
|
||||
import { getBackendSrv } from '@grafana/runtime';
|
||||
import { DashboardDTO } from 'app/types';
|
||||
|
||||
import { Resource } from '../apiserver/types';
|
||||
import { BASE_URL } from './api/baseAPI';
|
||||
import { AnnoKeyRepoName, AnnoKeyRepoPath, Resource } from '../apiserver/types';
|
||||
|
||||
/**
|
||||
* Load a dashboard from repository
|
||||
@@ -10,12 +11,12 @@ export async function loadDashboardFromProvisioning(repo: string, path: string):
|
||||
const params = new URLSearchParams(window.location.search);
|
||||
const ref = params.get('ref') ?? undefined; // commit hash or branch
|
||||
|
||||
const url = `apis/provisioning.grafana.app/v0alpha1/namespaces/${config.namespace}/repositories/${repo}/files/${path}`;
|
||||
const url = `${BASE_URL}/repositories/${repo}/files/${path}`;
|
||||
return getBackendSrv()
|
||||
.get(url, ref ? { ref } : undefined)
|
||||
.then((v) => {
|
||||
// Load the results from dryRun
|
||||
const dryRun = v.resource.dryRun as Resource<DashboardDataDTO, 'Dashboard'>;
|
||||
const dryRun = v.resource.dryRun;
|
||||
if (!dryRun) {
|
||||
return Promise.reject('failed to read provisioned dashboard');
|
||||
}
|
||||
@@ -24,6 +25,17 @@ export async function loadDashboardFromProvisioning(repo: string, path: string):
|
||||
return Promise.reject('unexpected resource type: ' + dryRun.apiVersion);
|
||||
}
|
||||
|
||||
// Make sure the annotation key exists
|
||||
let anno = dryRun.metadata.annotations
|
||||
if (!anno) {
|
||||
dryRun.metadata.annotations = anno = {}
|
||||
}
|
||||
anno[AnnoKeyRepoName] = repo
|
||||
anno[AnnoKeyRepoPath] = path
|
||||
if (ref) {
|
||||
anno[AnnoKeyRepoPath] += path + '#' + ref
|
||||
}
|
||||
|
||||
return {
|
||||
meta: {
|
||||
canStar: false,
|
||||
|
||||
Reference in new issue
Block a user