Provisioning Folder: Bugfix cancel action drawer should not reset selection state; Read only alert reoganize (#109087)
* Read only message on drawer, fix cancel drawer reset selection state. * Bugfix when select provisioned sub items from root, we are missing repo info
This commit is contained in:
@@ -148,7 +148,6 @@ export function BrowseActions({ folderDTO }: Props) {
|
||||
folderUid={folderDTO?.uid || ''}
|
||||
onDismiss={() => {
|
||||
setShowBulkDeleteProvisionedResource(false);
|
||||
onActionComplete();
|
||||
}}
|
||||
/>
|
||||
</Drawer>
|
||||
@@ -166,7 +165,6 @@ export function BrowseActions({ folderDTO }: Props) {
|
||||
folderUid={folderDTO?.uid}
|
||||
onDismiss={() => {
|
||||
setShowBulkMoveProvisionedResource(false);
|
||||
onActionComplete();
|
||||
}}
|
||||
/>
|
||||
</Drawer>
|
||||
|
||||
+2
-2
@@ -194,8 +194,8 @@ describe('BulkDeleteProvisionedResource', () => {
|
||||
});
|
||||
|
||||
it('returns null when repository is not available', () => {
|
||||
const { container } = setup(null);
|
||||
setup(null);
|
||||
|
||||
expect(container.firstChild).toBeNull();
|
||||
expect(screen.getByLabelText('Repository not found')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
+6
-3
@@ -25,7 +25,9 @@ import { collectSelectedItems, fetchProvisionedDashboardPath } from '../utils';
|
||||
import { MoveResultFailed } from './BulkActionFailureBanner';
|
||||
import { BulkActionPostSubmitStep } from './BulkActionPostSubmitStep';
|
||||
import { ProgressState } from './BulkActionProgress';
|
||||
import { RepoInvalidStateBanner } from './RepoInvalidStateBanner';
|
||||
import { useBulkActionRequest } from './useBulkActionRequest';
|
||||
import { useFolderNameFromSelection } from './useFolderNameFromSelection';
|
||||
import {
|
||||
BulkActionFormData,
|
||||
BulkActionProvisionResourceProps,
|
||||
@@ -202,7 +204,8 @@ export function BulkDeleteProvisionedResource({
|
||||
selectedItems,
|
||||
onDismiss,
|
||||
}: BulkActionProvisionResourceProps) {
|
||||
const { repository, folder } = useGetResourceRepositoryView({ folderName: folderUid });
|
||||
const folderName = useFolderNameFromSelection({ folderUid, selectedItems });
|
||||
const { repository, folder, isReadOnlyRepo } = useGetResourceRepositoryView({ folderName });
|
||||
|
||||
const workflowOptions = getWorkflowOptions(repository);
|
||||
const folderPath = folder?.metadata?.annotations?.[AnnoKeySourcePath] || '';
|
||||
@@ -214,8 +217,8 @@ export function BulkDeleteProvisionedResource({
|
||||
workflow: getDefaultWorkflow(repository),
|
||||
};
|
||||
|
||||
if (!repository) {
|
||||
return null;
|
||||
if (!repository || isReadOnlyRepo) {
|
||||
return <RepoInvalidStateBanner noRepository={!repository} isReadOnlyRepo={isReadOnlyRepo} />;
|
||||
}
|
||||
|
||||
return (
|
||||
|
||||
+7
-3
@@ -30,7 +30,9 @@ import { collectSelectedItems, fetchProvisionedDashboardPath } from '../utils';
|
||||
import { MoveResultFailed } from './BulkActionFailureBanner';
|
||||
import { BulkActionPostSubmitStep } from './BulkActionPostSubmitStep';
|
||||
import { ProgressState } from './BulkActionProgress';
|
||||
import { RepoInvalidStateBanner } from './RepoInvalidStateBanner';
|
||||
import { useBulkActionRequest } from './useBulkActionRequest';
|
||||
import { useFolderNameFromSelection } from './useFolderNameFromSelection';
|
||||
import {
|
||||
BulkActionFormData,
|
||||
BulkActionProvisionResourceProps,
|
||||
@@ -39,6 +41,7 @@ import {
|
||||
getResourceTargetPath,
|
||||
MoveResultSuccessState,
|
||||
} from './utils';
|
||||
|
||||
interface FormProps extends BulkActionProvisionResourceProps {
|
||||
initialValues: BulkActionFormData;
|
||||
repository: RepositoryView;
|
||||
@@ -277,7 +280,8 @@ function FormContent({ initialValues, selectedItems, repository, workflowOptions
|
||||
}
|
||||
|
||||
export function BulkMoveProvisionedResource({ folderUid, selectedItems, onDismiss }: BulkActionProvisionResourceProps) {
|
||||
const { repository, folder } = useGetResourceRepositoryView({ folderName: folderUid });
|
||||
const folderName = useFolderNameFromSelection({ folderUid, selectedItems });
|
||||
const { repository, folder, isReadOnlyRepo } = useGetResourceRepositoryView({ folderName });
|
||||
|
||||
const workflowOptions = getWorkflowOptions(repository);
|
||||
const folderPath = folder?.metadata?.annotations?.[AnnoKeySourcePath] || '';
|
||||
@@ -289,8 +293,8 @@ export function BulkMoveProvisionedResource({ folderUid, selectedItems, onDismis
|
||||
workflow: getDefaultWorkflow(repository),
|
||||
};
|
||||
|
||||
if (!repository) {
|
||||
return null;
|
||||
if (!repository || isReadOnlyRepo) {
|
||||
return <RepoInvalidStateBanner noRepository={!repository} isReadOnlyRepo={isReadOnlyRepo} />;
|
||||
}
|
||||
|
||||
return (
|
||||
|
||||
+44
@@ -0,0 +1,44 @@
|
||||
import { Trans, t } from '@grafana/i18n';
|
||||
import { Alert } from '@grafana/ui';
|
||||
|
||||
interface Props {
|
||||
noRepository: boolean;
|
||||
isReadOnlyRepo: boolean;
|
||||
readOnlyMessage?: string;
|
||||
}
|
||||
|
||||
export function RepoInvalidStateBanner({ noRepository, isReadOnlyRepo, readOnlyMessage }: Props) {
|
||||
if (noRepository) {
|
||||
return (
|
||||
<Alert
|
||||
title={t('browse-dashboards.bulk-move-resources-form.error.repository-not-found-title', 'Repository not found')}
|
||||
>
|
||||
<Trans i18nKey="browse-dashboards.bulk-move-resources-form.error.repository-not-found-message">
|
||||
The repository for the selected folder could not be found. Please ensure that the folder is provisioned
|
||||
correctly.
|
||||
</Trans>
|
||||
</Alert>
|
||||
);
|
||||
}
|
||||
|
||||
if (isReadOnlyRepo) {
|
||||
return (
|
||||
<Alert
|
||||
title={t('browse-dashboards.bulk-move-resources-form.error.read-only-title', 'This repository is read only')}
|
||||
>
|
||||
{readOnlyMessage
|
||||
? t(
|
||||
'browse-dashboards.bulk-move-resources-form.error.read-only-saving-message',
|
||||
'Repository is read-only and provisioned in git. {{readOnlyMessage}}',
|
||||
{ readOnlyMessage }
|
||||
)
|
||||
: t(
|
||||
'browse-dashboards.bulk-move-resources-form.error.read-only-message',
|
||||
'If you have direct access to the target, please make modifications directly in the target repository.'
|
||||
)}
|
||||
</Alert>
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
+49
@@ -0,0 +1,49 @@
|
||||
import { useMemo } from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
|
||||
import { ManagerKind } from 'app/features/apiserver/types';
|
||||
|
||||
import { rootItemsSelector, useChildrenByParentUIDState } from '../../state/hooks';
|
||||
import { findItem } from '../../state/utils';
|
||||
import { DashboardTreeSelection } from '../../types';
|
||||
|
||||
// This hook retrieves the folder UID from the selection state. Because search endpoint currently does not return resource metadata
|
||||
// NOTE: This is a temporary workaround until the search endpoint is updated
|
||||
interface Props {
|
||||
folderUid?: string;
|
||||
selectedItems: Omit<DashboardTreeSelection, 'panel' | '$all'>;
|
||||
}
|
||||
export function useFolderNameFromSelection({ folderUid, selectedItems }: Props) {
|
||||
const rootItems = useSelector(rootItemsSelector);
|
||||
const childrenByParentUID = useChildrenByParentUIDState();
|
||||
|
||||
return useMemo(() => {
|
||||
// if we already have a folderUid, return it;
|
||||
if (folderUid) {
|
||||
return folderUid;
|
||||
}
|
||||
|
||||
// Helper to walk up tree and find provisioned folder
|
||||
const findProvisionedParent = (itemUid: string): string | undefined => {
|
||||
const item = findItem(rootItems?.items || [], childrenByParentUID, itemUid);
|
||||
if (!item) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (item.managedBy === ManagerKind.Repo) {
|
||||
return item.uid;
|
||||
}
|
||||
if (item.parentUID) {
|
||||
return findProvisionedParent(item.parentUID);
|
||||
}
|
||||
return undefined;
|
||||
};
|
||||
|
||||
// Try folders, then dashboards
|
||||
const firstSelectedUid =
|
||||
Object.keys(selectedItems.folder).find((uid) => selectedItems.folder[uid]) ||
|
||||
Object.keys(selectedItems.dashboard).find((uid) => selectedItems.dashboard[uid]);
|
||||
|
||||
return firstSelectedUid ? findProvisionedParent(firstSelectedUid) : undefined;
|
||||
}, [folderUid, selectedItems, rootItems, childrenByParentUID]);
|
||||
}
|
||||
@@ -134,6 +134,7 @@ const defaultHookData: ProvisionedFolderFormDataResult = {
|
||||
repository: mockRepository,
|
||||
folder: mockFolder,
|
||||
initialValues: mockFormData,
|
||||
isReadOnlyRepo: false,
|
||||
};
|
||||
|
||||
function setup(
|
||||
|
||||
@@ -20,6 +20,7 @@ import { FolderDTO } from 'app/types/folders';
|
||||
import { useProvisionedFolderFormData } from '../hooks/useProvisionedFolderFormData';
|
||||
|
||||
import { DescendantCount } from './BrowseActions/DescendantCount';
|
||||
import { RepoInvalidStateBanner } from './BulkActions/RepoInvalidStateBanner';
|
||||
import { getFolderURL } from './utils';
|
||||
|
||||
interface FormProps extends DeleteProvisionedFolderFormProps {
|
||||
@@ -150,14 +151,23 @@ function FormContent({ initialValues, parentFolder, repository, workflowOptions,
|
||||
}
|
||||
|
||||
export function DeleteProvisionedFolderForm({ parentFolder, onDismiss }: DeleteProvisionedFolderFormProps) {
|
||||
const { workflowOptions, repository, folder, initialValues } = useProvisionedFolderFormData({
|
||||
const { workflowOptions, repository, folder, initialValues, isReadOnlyRepo } = useProvisionedFolderFormData({
|
||||
folderUid: parentFolder?.uid,
|
||||
action: 'delete',
|
||||
title: parentFolder?.title,
|
||||
});
|
||||
|
||||
if (!initialValues) {
|
||||
return null;
|
||||
if (isReadOnlyRepo || !initialValues) {
|
||||
return (
|
||||
<RepoInvalidStateBanner
|
||||
noRepository={!initialValues}
|
||||
isReadOnlyRepo={isReadOnlyRepo}
|
||||
readOnlyMessage={t(
|
||||
'browse-dashboards.delete-folder.read-only-message',
|
||||
'To delete this folder, please remove the folder from your repository.'
|
||||
)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
|
||||
@@ -131,6 +131,7 @@ const mockHookData: ProvisionedFolderFormDataResult = {
|
||||
workflows: ['write', 'branch'],
|
||||
target: 'folder',
|
||||
},
|
||||
isReadOnlyRepo: false,
|
||||
folder: {
|
||||
metadata: {
|
||||
annotations: {
|
||||
@@ -188,18 +189,18 @@ describe('NewProvisionedFolderForm', () => {
|
||||
});
|
||||
|
||||
it('should return null when initialValues is not available', () => {
|
||||
const { container } = setup(
|
||||
setup(
|
||||
{},
|
||||
{
|
||||
...mockHookData,
|
||||
initialValues: undefined,
|
||||
}
|
||||
);
|
||||
expect(container.firstChild).toBeNull();
|
||||
expect(screen.getByLabelText('Repository not found')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should show error when repository is not found', () => {
|
||||
const { container } = setup(
|
||||
setup(
|
||||
{},
|
||||
{
|
||||
...mockHookData,
|
||||
@@ -207,7 +208,7 @@ describe('NewProvisionedFolderForm', () => {
|
||||
initialValues: undefined,
|
||||
}
|
||||
);
|
||||
expect(container.firstChild).toBeNull();
|
||||
expect(screen.getByLabelText('Repository not found')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should show branch field when branch workflow is selected', async () => {
|
||||
|
||||
@@ -22,6 +22,7 @@ import { FolderDTO } from 'app/types/folders';
|
||||
|
||||
import { useProvisionedFolderFormData } from '../hooks/useProvisionedFolderFormData';
|
||||
|
||||
import { RepoInvalidStateBanner } from './BulkActions/RepoInvalidStateBanner';
|
||||
import { validateFolderName } from './NewFolderForm';
|
||||
import { formatFolderName, hasFolderNameCharactersToReplace } from './utils';
|
||||
|
||||
@@ -213,14 +214,23 @@ function FormContent({ initialValues, repository, workflowOptions, folder, onDis
|
||||
}
|
||||
|
||||
export function NewProvisionedFolderForm({ parentFolder, onDismiss }: Props) {
|
||||
const { workflowOptions, repository, folder, initialValues } = useProvisionedFolderFormData({
|
||||
const { workflowOptions, repository, folder, initialValues, isReadOnlyRepo } = useProvisionedFolderFormData({
|
||||
folderUid: parentFolder?.uid,
|
||||
action: 'create',
|
||||
title: '', // Empty title for new folders
|
||||
});
|
||||
|
||||
if (!initialValues) {
|
||||
return null;
|
||||
if (isReadOnlyRepo || !initialValues) {
|
||||
return (
|
||||
<RepoInvalidStateBanner
|
||||
noRepository={!initialValues}
|
||||
isReadOnlyRepo={isReadOnlyRepo}
|
||||
readOnlyMessage={t(
|
||||
'browse-dashboards.new-folder.read-only-message',
|
||||
'To create this folder, please add the resource in your repository directly.'
|
||||
)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
|
||||
@@ -20,6 +20,7 @@ export interface ProvisionedFolderFormDataResult {
|
||||
folder?: Folder;
|
||||
workflowOptions: Array<{ label: string; value: string }>;
|
||||
initialValues?: BaseProvisionedFormData;
|
||||
isReadOnlyRepo: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -30,7 +31,7 @@ export function useProvisionedFolderFormData({
|
||||
action,
|
||||
title,
|
||||
}: UseProvisionedFolderFormDataProps): ProvisionedFolderFormDataResult {
|
||||
const { repository, folder, isLoading } = useGetResourceRepositoryView({ folderName: folderUid });
|
||||
const { repository, folder, isLoading, isReadOnlyRepo } = useGetResourceRepositoryView({ folderName: folderUid });
|
||||
|
||||
const workflowOptions = getWorkflowOptions(repository);
|
||||
const timestamp = generateTimestamp();
|
||||
@@ -56,5 +57,6 @@ export function useProvisionedFolderFormData({
|
||||
folder,
|
||||
workflowOptions,
|
||||
initialValues,
|
||||
isReadOnlyRepo,
|
||||
};
|
||||
}
|
||||
|
||||
+11
-14
@@ -6,11 +6,12 @@ import { AppEvents, locationUtil } from '@grafana/data';
|
||||
import { Trans, t } from '@grafana/i18n';
|
||||
import { getAppEvents, locationService } from '@grafana/runtime';
|
||||
import { Dashboard } from '@grafana/schema';
|
||||
import { Alert, Button, Field, Input, Stack, TextArea } from '@grafana/ui';
|
||||
import { Button, Field, Input, Stack, TextArea } from '@grafana/ui';
|
||||
import { RepositoryView } from 'app/api/clients/provisioning/v0alpha1';
|
||||
import { FolderPicker } from 'app/core/components/Select/FolderPicker';
|
||||
import kbn from 'app/core/utils/kbn';
|
||||
import { Resource } from 'app/features/apiserver/types';
|
||||
import { RepoInvalidStateBanner } from 'app/features/browse-dashboards/components/BulkActions/RepoInvalidStateBanner';
|
||||
import { validationSrv } from 'app/features/manage-dashboards/services/ValidationSrv';
|
||||
import { PROVISIONING_URL } from 'app/features/provisioning/constants';
|
||||
import { useCreateOrUpdateRepositoryFile } from 'app/features/provisioning/hooks/useCreateOrUpdateRepositoryFile';
|
||||
@@ -60,7 +61,7 @@ export function SaveProvisionedDashboardForm({
|
||||
reset(defaultValues);
|
||||
}, [defaultValues, reset]);
|
||||
|
||||
const onRequestError = (error: unknown, info: ProvisionedOperationInfo) => {
|
||||
const onRequestError = (error: unknown) => {
|
||||
appEvents.publish({
|
||||
type: AppEvents.alertError.name,
|
||||
payload: [t('dashboard-scene.save-provisioned-dashboard-form.api-error', 'Error saving dashboard'), error],
|
||||
@@ -80,6 +81,7 @@ export function SaveProvisionedDashboardForm({
|
||||
};
|
||||
|
||||
const onWriteSuccess = (_: ProvisionedOperationInfo, upsert: Resource<Dashboard>) => {
|
||||
handleDismiss();
|
||||
if (isNew && upsert?.metadata.name) {
|
||||
handleNewDashboard(upsert);
|
||||
} else {
|
||||
@@ -91,6 +93,7 @@ export function SaveProvisionedDashboardForm({
|
||||
};
|
||||
|
||||
const onBranchSuccess = (ref: string, path: string, info: ProvisionedOperationInfo, upsert: Resource<Dashboard>) => {
|
||||
handleDismiss();
|
||||
if (isNew && upsert?.metadata?.name) {
|
||||
handleNewDashboard(upsert);
|
||||
} else {
|
||||
@@ -104,7 +107,7 @@ export function SaveProvisionedDashboardForm({
|
||||
}
|
||||
};
|
||||
|
||||
const onDismiss = () => {
|
||||
const handleDismiss = () => {
|
||||
dashboard.setState({ isDirty: false });
|
||||
panelEditor?.onDiscard();
|
||||
drawer.onClose();
|
||||
@@ -118,7 +121,6 @@ export function SaveProvisionedDashboardForm({
|
||||
onBranchSuccess: ({ ref, path }, info, resource) => onBranchSuccess(ref, path, info, resource),
|
||||
onWriteSuccess,
|
||||
onError: onRequestError,
|
||||
onDismiss,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -157,16 +159,11 @@ export function SaveProvisionedDashboardForm({
|
||||
<form onSubmit={handleSubmit(handleFormSubmit)} name="save-provisioned-form">
|
||||
<Stack direction="column" gap={2}>
|
||||
{readOnly && (
|
||||
<Alert
|
||||
title={t(
|
||||
'dashboard-scene.save-provisioned-dashboard-form.title-this-repository-is-read-only',
|
||||
'This repository is read only'
|
||||
)}
|
||||
>
|
||||
<Trans i18nKey="dashboard-scene.save-provisioned-dashboard-form.copy-json-message">
|
||||
If you have direct access to the target, copy the JSON and paste it there.
|
||||
</Trans>
|
||||
</Alert>
|
||||
<RepoInvalidStateBanner
|
||||
noRepository={false}
|
||||
isReadOnlyRepo={true}
|
||||
readOnlyMessage="If you have direct access to the target, copy the JSON and paste it there."
|
||||
/>
|
||||
)}
|
||||
|
||||
{isNew && (
|
||||
|
||||
@@ -4,6 +4,7 @@ import { RepositoryView } from 'app/api/clients/provisioning/v0alpha1';
|
||||
import { useUrlParams } from 'app/core/navigation/hooks';
|
||||
import { AnnoKeyManagerIdentity, AnnoKeyManagerKind, AnnoKeySourcePath } from 'app/features/apiserver/types';
|
||||
import { useGetResourceRepositoryView } from 'app/features/provisioning/hooks/useGetResourceRepositoryView';
|
||||
import { getIsReadOnlyRepo } from 'app/features/provisioning/utils/repository';
|
||||
import { DashboardMeta } from 'app/types/dashboard';
|
||||
|
||||
import { DashboardScene } from '../../scene/DashboardScene';
|
||||
@@ -110,8 +111,6 @@ export function useProvisionedDashboardData(dashboard: DashboardScene): Provisio
|
||||
const { values, isNew, repository } = defaultValuesResult;
|
||||
const workflowOptions = getWorkflowOptions(repository, loadedFromRef);
|
||||
|
||||
const readOnly = !repository?.workflows?.length;
|
||||
|
||||
return {
|
||||
isReady: true,
|
||||
defaultValues: values,
|
||||
@@ -119,7 +118,7 @@ export function useProvisionedDashboardData(dashboard: DashboardScene): Provisio
|
||||
loadedFromRef,
|
||||
workflowOptions,
|
||||
isNew,
|
||||
readOnly,
|
||||
readOnly: getIsReadOnlyRepo(repository),
|
||||
isLoading,
|
||||
setIsLoading,
|
||||
};
|
||||
|
||||
@@ -4,8 +4,9 @@ import { useNavigate } from 'react-router-dom-v5-compat';
|
||||
import { AppEvents } from '@grafana/data';
|
||||
import { Trans, t } from '@grafana/i18n';
|
||||
import { getAppEvents } from '@grafana/runtime';
|
||||
import { Alert, Button, Drawer, Stack } from '@grafana/ui';
|
||||
import { Button, Drawer, Stack } from '@grafana/ui';
|
||||
import { RepositoryView, useDeleteRepositoryFilesWithPathMutation } from 'app/api/clients/provisioning/v0alpha1';
|
||||
import { RepoInvalidStateBanner } from 'app/features/browse-dashboards/components/BulkActions/RepoInvalidStateBanner';
|
||||
import { PROVISIONING_URL } from 'app/features/provisioning/constants';
|
||||
|
||||
import { ResourceEditFormSharedFields } from '../components/Provisioned/ResourceEditFormSharedFields';
|
||||
@@ -118,17 +119,11 @@ export function DeleteProvisionedDashboardForm({
|
||||
<form onSubmit={handleSubmit(handleSubmitForm)}>
|
||||
<Stack direction="column" gap={2}>
|
||||
{readOnly && (
|
||||
<Alert
|
||||
title={t(
|
||||
'dashboard-scene.delete-provisioned-dashboard-form.title-this-repository-is-read-only',
|
||||
'This repository is read only'
|
||||
)}
|
||||
>
|
||||
<Trans i18nKey="dashboard-scene.delete-provisioned-dashboard-form.delete-read-only-file-message">
|
||||
This dashboard cannot be deleted directly from Grafana because the repository is read-only. To delete
|
||||
this dashboard, please remove the file from your Git repository.
|
||||
</Trans>
|
||||
</Alert>
|
||||
<RepoInvalidStateBanner
|
||||
noRepository={false}
|
||||
isReadOnlyRepo={true}
|
||||
readOnlyMessage="To delete this dashboard, please remove the file from your repository."
|
||||
/>
|
||||
)}
|
||||
|
||||
<ResourceEditFormSharedFields
|
||||
|
||||
@@ -5,6 +5,8 @@ import { Folder, useGetFolderQuery } from 'app/api/clients/folder/v1beta1';
|
||||
import { RepositoryView, useGetFrontendSettingsQuery } from 'app/api/clients/provisioning/v0alpha1';
|
||||
import { AnnoKeyManagerIdentity } from 'app/features/apiserver/types';
|
||||
|
||||
import { getIsReadOnlyRepo } from '../utils/repository';
|
||||
|
||||
interface GetResourceRepositoryArgs {
|
||||
name?: string; // the repository name
|
||||
folderName?: string; // folder we are targeting
|
||||
@@ -15,6 +17,7 @@ interface RepositoryViewData {
|
||||
folder?: Folder;
|
||||
isLoading?: boolean;
|
||||
isInstanceManaged: boolean;
|
||||
isReadOnlyRepo: boolean;
|
||||
}
|
||||
|
||||
// This is safe to call as a viewer (you do not need full access to the Repository configs)
|
||||
@@ -29,17 +32,17 @@ export const useGetResourceRepositoryView = ({ name, folderName }: GetResourceRe
|
||||
);
|
||||
|
||||
if (!provisioningEnabled) {
|
||||
return { isLoading: false, isInstanceManaged: false };
|
||||
return { isLoading: false, isInstanceManaged: false, isReadOnlyRepo: false };
|
||||
}
|
||||
|
||||
if (isSettingsLoading || isFolderLoading) {
|
||||
return { isLoading: true, isInstanceManaged: false };
|
||||
return { isLoading: true, isInstanceManaged: false, isReadOnlyRepo: false };
|
||||
}
|
||||
|
||||
const items = settingsData?.items ?? [];
|
||||
|
||||
if (!items.length) {
|
||||
return { folder, isInstanceManaged: false };
|
||||
return { folder, isInstanceManaged: false, isReadOnlyRepo: false };
|
||||
}
|
||||
|
||||
const instanceRepo = items.find((repo) => repo.target === 'instance');
|
||||
@@ -52,6 +55,7 @@ export const useGetResourceRepositoryView = ({ name, folderName }: GetResourceRe
|
||||
repository,
|
||||
folder,
|
||||
isInstanceManaged,
|
||||
isReadOnlyRepo: getIsReadOnlyRepo(repository),
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -65,6 +69,7 @@ export const useGetResourceRepositoryView = ({ name, folderName }: GetResourceRe
|
||||
repository,
|
||||
folder,
|
||||
isInstanceManaged,
|
||||
isReadOnlyRepo: getIsReadOnlyRepo(repository),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -77,6 +82,7 @@ export const useGetResourceRepositoryView = ({ name, folderName }: GetResourceRe
|
||||
repository,
|
||||
folder,
|
||||
isInstanceManaged,
|
||||
isReadOnlyRepo: getIsReadOnlyRepo(repository),
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -86,5 +92,6 @@ export const useGetResourceRepositoryView = ({ name, folderName }: GetResourceRe
|
||||
repository: instanceRepo,
|
||||
folder,
|
||||
isInstanceManaged,
|
||||
isReadOnlyRepo: getIsReadOnlyRepo(instanceRepo),
|
||||
};
|
||||
};
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
import { RepositoryView } from 'app/api/clients/provisioning/v0alpha1';
|
||||
|
||||
export function getIsReadOnlyRepo(repository: RepositoryView | undefined): boolean {
|
||||
if (!repository) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Repository is consider read-only if it has no workflows defined (workflows are required for write operations)
|
||||
return repository.workflows.length === 0;
|
||||
}
|
||||
@@ -3539,6 +3539,13 @@
|
||||
"button-move": "Move",
|
||||
"button-moving": "Moving...",
|
||||
"deleting": "Deleting",
|
||||
"error": {
|
||||
"read-only-message": "If you have direct access to the target, please make modifications directly in the target repository.",
|
||||
"read-only-saving-message": "Repository is read-only and provisioned in git. {{readOnlyMessage}}",
|
||||
"read-only-title": "This repository is read only",
|
||||
"repository-not-found-message": "The repository for the selected folder could not be found. Please ensure that the folder is provisioned correctly.",
|
||||
"repository-not-found-title": "Repository not found"
|
||||
},
|
||||
"error-file-content-not-found": "File content not found",
|
||||
"error-path-not-found": "Path not found",
|
||||
"error-target-folder-path-missing": "Target folder path is missing",
|
||||
@@ -3568,6 +3575,9 @@
|
||||
"select-checkbox": "Select",
|
||||
"tags-column": "Tags"
|
||||
},
|
||||
"delete-folder": {
|
||||
"read-only-message": "To delete this folder, please remove the folder from your repository."
|
||||
},
|
||||
"delete-provisioned-folder-form": {
|
||||
"api-error": "Failed to delete folder",
|
||||
"button-cancel": "Cancel",
|
||||
@@ -3611,6 +3621,9 @@
|
||||
"name-cell": {
|
||||
"no-items": "No items"
|
||||
},
|
||||
"new-folder": {
|
||||
"read-only-message": "To create this folder, please add the resource in your repository directly."
|
||||
},
|
||||
"new-folder-form": {
|
||||
"cancel-label": "Cancel",
|
||||
"create-label": "Create",
|
||||
@@ -5691,11 +5704,9 @@
|
||||
"api-error": "Failed to delete dashboard",
|
||||
"cancel-action": "Cancel",
|
||||
"delete-action": "Delete dashboard",
|
||||
"delete-read-only-file-message": "This dashboard cannot be deleted directly from Grafana because the repository is read-only. To delete this dashboard, please remove the file from your Git repository.",
|
||||
"deleting": "Deleting...",
|
||||
"drawer-title": "Delete Provisioned Dashboard",
|
||||
"success-message": "Dashboard deleted successfully",
|
||||
"title-this-repository-is-read-only": "This repository is read only"
|
||||
"success-message": "Dashboard deleted successfully"
|
||||
},
|
||||
"description-label": {
|
||||
"description": "Description"
|
||||
@@ -6067,7 +6078,6 @@
|
||||
"api-error": "Error saving dashboard",
|
||||
"cancel": "Cancel",
|
||||
"cannot-be-saved": "This dashboard cannot be saved from the Grafana UI because it has been provisioned from another source. Copy the JSON or save it to a file below, then you can update your dashboard in the provisioning source.",
|
||||
"copy-json-message": "If you have direct access to the target, copy the JSON and paste it there.",
|
||||
"copy-json-to-clipboard": "Copy JSON to clipboard",
|
||||
"file-path": "<0>File path:</0> {{filePath}}",
|
||||
"label-description": "Description",
|
||||
@@ -6079,7 +6089,6 @@
|
||||
"see-docs": "See <2>documentation</2> for more information about provisioning.",
|
||||
"title-required": "Dashboard title is required",
|
||||
"title-same-as-folder": "Dashboard name cannot be the same as the folder name",
|
||||
"title-this-repository-is-read-only": "This repository is read only",
|
||||
"title-validation-failed": "Dashboard title validation failed."
|
||||
},
|
||||
"scenes-new-rule-from-panel-button": {
|
||||
|
||||
Reference in New Issue
Block a user