diff --git a/.betterer.results b/.betterer.results
index d48b7f33ac0..447ddf16d5f 100644
--- a/.betterer.results
+++ b/.betterer.results
@@ -5764,7 +5764,10 @@ exports[`better eslint`] = {
[0, 0, 0, "No untranslated strings. Wrap text with ", "10"]
],
"public/app/features/provisioning/JobStatus.tsx:5381": [
- [0, 0, 0, "No untranslated strings. Wrap text with ", "0"]
+ [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "0"],
+ [0, 0, 0, "No untranslated strings. Wrap text with ", "1"],
+ [0, 0, 0, "No untranslated strings. Wrap text with ", "2"],
+ [0, 0, 0, "No untranslated strings. Wrap text with ", "3"]
],
"public/app/features/provisioning/MigrateToRepository.tsx:5381": [
[0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "0"],
@@ -5776,6 +5779,14 @@ exports[`better eslint`] = {
[0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "6"],
[0, 0, 0, "No untranslated strings. Wrap text with ", "7"]
],
+ "public/app/features/provisioning/OnboardingPage.tsx:5381": [
+ [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "0"],
+ [0, 0, 0, "No untranslated strings. Wrap text with ", "1"],
+ [0, 0, 0, "No untranslated strings. Wrap text with ", "2"],
+ [0, 0, 0, "No untranslated strings. Wrap text with ", "3"],
+ [0, 0, 0, "No untranslated strings. Wrap text with ", "4"],
+ [0, 0, 0, "No untranslated strings. Wrap text with ", "5"]
+ ],
"public/app/features/provisioning/RecentJobs.tsx:5381": [
[0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "0"],
[0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "1"],
@@ -5793,14 +5804,15 @@ exports[`better eslint`] = {
"public/app/features/provisioning/RepositoryListPage.tsx:5381": [
[0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "0"],
[0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "1"],
- [0, 0, 0, "No untranslated strings. Wrap text with ", "2"],
+ [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "2"],
[0, 0, 0, "No untranslated strings. Wrap text with ", "3"],
[0, 0, 0, "No untranslated strings. Wrap text with ", "4"],
[0, 0, 0, "No untranslated strings. Wrap text with ", "5"],
[0, 0, 0, "No untranslated strings. Wrap text with ", "6"],
[0, 0, 0, "No untranslated strings. Wrap text with ", "7"],
[0, 0, 0, "No untranslated strings. Wrap text with ", "8"],
- [0, 0, 0, "No untranslated strings. Wrap text with ", "9"]
+ [0, 0, 0, "No untranslated strings. Wrap text with ", "9"],
+ [0, 0, 0, "No untranslated strings. Wrap text with ", "10"]
],
"public/app/features/provisioning/RepositoryOverview.tsx:5381": [
[0, 0, 0, "No untranslated strings. Wrap text with ", "0"],
@@ -5884,9 +5896,7 @@ exports[`better eslint`] = {
[0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "3"],
[0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "4"],
[0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "5"],
- [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "6"],
- [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "7"],
- [0, 0, 0, "No untranslated strings. Wrap text with ", "8"]
+ [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "6"]
],
"public/app/features/provisioning/Wizard/MigrateStep.tsx:5381": [
[0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "0"],
@@ -5894,10 +5904,12 @@ exports[`better eslint`] = {
[0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "2"],
[0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "3"],
[0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "4"],
- [0, 0, 0, "No untranslated strings. Wrap text with ", "5"],
+ [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "5"],
[0, 0, 0, "No untranslated strings. Wrap text with ", "6"],
[0, 0, 0, "No untranslated strings. Wrap text with ", "7"],
- [0, 0, 0, "No untranslated strings. Wrap text with ", "8"]
+ [0, 0, 0, "No untranslated strings. Wrap text with ", "8"],
+ [0, 0, 0, "No untranslated strings. Wrap text with ", "9"],
+ [0, 0, 0, "No untranslated strings. Wrap text with ", "10"]
],
"public/app/features/provisioning/Wizard/ProvisioningWizard.tsx:5381": [
[0, 0, 0, "No untranslated strings. Wrap text with ", "0"]
@@ -5921,9 +5933,6 @@ exports[`better eslint`] = {
"public/app/features/provisioning/Wizard/Stepper.tsx:5381": [
[0, 0, 0, "No untranslated strings. Wrap text with ", "0"]
],
- "public/app/features/provisioning/Wizard/WizardPage.tsx:5381": [
- [0, 0, 0, "Unexpected any. Specify a different type.", "0"]
- ],
"public/app/features/provisioning/api/index.ts:5381": [
[0, 0, 0, "Do not use export all (\`export * from ...\`)", "0"],
[0, 0, 0, "Unexpected any. Specify a different type.", "1"],
diff --git a/public/app/features/provisioning/OnboardingPage.tsx b/public/app/features/provisioning/OnboardingPage.tsx
new file mode 100644
index 00000000000..2a7ce2a3110
--- /dev/null
+++ b/public/app/features/provisioning/OnboardingPage.tsx
@@ -0,0 +1,43 @@
+import SVG from 'react-inlinesvg';
+
+import { EmptyState, LinkButton, Alert, Stack, Text } from '@grafana/ui';
+import { Page } from 'app/core/components/Page/Page';
+
+import { SetupWarnings } from './SetupWarnings';
+import { MIGRATE_URL } from './constants';
+
+export default function OnboardingPage() {
+ return (
+
+
+
+
+ When you connect your whole instance, depending on its size, the setup might make the dashboard unavailable to
+ users for up to 30 minutes. We recommend warning your users before starting the process. You can use the
+ announcement banner or your preferred channels.
+
+ }
+ button={
+
+ Connect Grafana to repository
+
+ }
+ >
+
+ Store and provision your Grafana resources externally by connecting to a repository.
+ We currently support GitHub and local storage.
+
+ Learn more
+
+
+
+
+
+ );
+}
diff --git a/public/app/features/provisioning/RepositoryListPage.tsx b/public/app/features/provisioning/RepositoryListPage.tsx
index cd34ad87f8c..4aff01663d4 100644
--- a/public/app/features/provisioning/RepositoryListPage.tsx
+++ b/public/app/features/provisioning/RepositoryListPage.tsx
@@ -1,6 +1,7 @@
import { ReactNode, useEffect, useState } from 'react';
-import { useNavigate } from 'react-router-dom-v5-compat';
+import { AppEvents } from '@grafana/data';
+import { getAppEvents } from '@grafana/runtime';
import {
Card,
EmptySearchResult,
@@ -12,33 +13,69 @@ import {
Stack,
TextLink,
Text,
+ Alert,
+ ConfirmModal,
} from '@grafana/ui';
import { Page } from 'app/core/components/Page/Page';
import { DeleteRepositoryButton } from './DeleteRepositoryButton';
+import OnboardingPage from './OnboardingPage';
import { SetupWarnings } from './SetupWarnings';
import { StatusBadge } from './StatusBadge';
import { SyncRepository } from './SyncRepository';
-import { Repository, ResourceCount, useGetFrontendSettingsQuery } from './api';
+import { Repository, ResourceCount, useDeletecollectionRepositoryMutation, useGetFrontendSettingsQuery } from './api';
import { NEW_URL, PROVISIONING_URL } from './constants';
import { useRepositoryList } from './hooks';
+const appEvents = getAppEvents();
+
export default function RepositoryListPage() {
const [items, isLoading] = useRepositoryList({ watch: true });
const settings = useGetFrontendSettingsQuery();
- const navigate = useNavigate();
+ const [deleteAll, deleteAllResult] = useDeletecollectionRepositoryMutation();
+ const [showDeleteModal, setShowDeleteModal] = useState(false);
useEffect(() => {
- if (settings.data?.legacyStorage && !items?.length) {
- navigate('/admin/provisioning/setup');
+ if (deleteAllResult.isSuccess) {
+ appEvents.publish({
+ type: AppEvents.alertSuccess.name,
+ payload: ['All configured repositories deleted'],
+ });
}
- }, [settings, navigate, items?.length]);
+ }, [deleteAllResult.isSuccess]);
+
+ if (!items?.length && !isLoading) {
+ return ;
+ }
+
+ const onConfirmDelete = () => {
+ deleteAll({ deleteOptions: {} });
+ setShowDeleteModal(false);
+ };
return (
+ {settings.data?.legacyStorage && (
+ Remove all configured repositories>}
+ onRemove={() => setShowDeleteModal(true)}
+ >
+ Configured repositories will not work while running legacy storage.
+
+ )}
+ setShowDeleteModal(false)}
+ />
);
diff --git a/public/app/features/provisioning/Wizard/WizardPage.tsx b/public/app/features/provisioning/Wizard/MigrateToProvisioningPage.tsx
similarity index 68%
rename from public/app/features/provisioning/Wizard/WizardPage.tsx
rename to public/app/features/provisioning/Wizard/MigrateToProvisioningPage.tsx
index 9cd2f2c63d7..d56c85862ae 100644
--- a/public/app/features/provisioning/Wizard/WizardPage.tsx
+++ b/public/app/features/provisioning/Wizard/MigrateToProvisioningPage.tsx
@@ -4,11 +4,11 @@ import { SetupWarnings } from '../SetupWarnings';
import { ProvisioningWizard } from './ProvisioningWizard';
-export default function NewRepositoryPage() {
+export default function MigrateToProvisioningPage() {
return (
diff --git a/public/app/features/provisioning/api/endpoints.gen.ts b/public/app/features/provisioning/api/endpoints.gen.ts
index 311c78f062f..7817a0eb4c5 100644
--- a/public/app/features/provisioning/api/endpoints.gen.ts
+++ b/public/app/features/provisioning/api/endpoints.gen.ts
@@ -67,6 +67,30 @@ const injectedRtkApi = api
}),
invalidatesTags: ['Repository'],
}),
+ deletecollectionRepository: build.mutation({
+ query: (queryArg) => ({
+ url: `/repositories`,
+ method: 'DELETE',
+ body: queryArg.deleteOptions,
+ params: {
+ pretty: queryArg.pretty,
+ continue: queryArg['continue'],
+ dryRun: queryArg.dryRun,
+ fieldSelector: queryArg.fieldSelector,
+ gracePeriodSeconds: queryArg.gracePeriodSeconds,
+ ignoreStoreReadErrorWithClusterBreakingPotential: queryArg.ignoreStoreReadErrorWithClusterBreakingPotential,
+ labelSelector: queryArg.labelSelector,
+ limit: queryArg.limit,
+ orphanDependents: queryArg.orphanDependents,
+ propagationPolicy: queryArg.propagationPolicy,
+ resourceVersion: queryArg.resourceVersion,
+ resourceVersionMatch: queryArg.resourceVersionMatch,
+ sendInitialEvents: queryArg.sendInitialEvents,
+ timeoutSeconds: queryArg.timeoutSeconds,
+ },
+ }),
+ invalidatesTags: ['Repository'],
+ }),
getRepository: build.query({
query: (queryArg) => ({
url: `/repositories/${queryArg.name}`,
@@ -361,6 +385,58 @@ export type CreateRepositoryArg = {
fieldValidation?: string;
repository: Repository;
};
+export type DeletecollectionRepositoryResponse = /** status 200 OK */ Status;
+export type DeletecollectionRepositoryArg = {
+ /** If 'true', then the output is pretty printed. Defaults to 'false' unless the user-agent indicates a browser or command-line HTTP tool (curl and wget). */
+ pretty?: string;
+ /** The continue option should be set when retrieving more results from the server. Since this value is server defined, clients may only use the continue value from a previous query result with identical query parameters (except for the value of continue) and the server may reject a continue value it does not recognize. If the specified continue value is no longer valid whether due to expiration (generally five to fifteen minutes) or a configuration change on the server, the server will respond with a 410 ResourceExpired error together with a continue token. If the client needs a consistent list, it must restart their list without the continue field. Otherwise, the client may send another list request with the token received with the 410 error, the server will respond with a list starting from the next key, but from the latest snapshot, which is inconsistent from the previous list results - objects that are created, modified, or deleted after the first list request will be included in the response, as long as their keys are after the "next key".
+
+ This field is not supported when watch is true. Clients may start a watch from the last resourceVersion value returned by the server and not miss any modifications. */
+ continue?: string;
+ /** When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed */
+ dryRun?: string;
+ /** A selector to restrict the list of returned objects by their fields. Defaults to everything. */
+ fieldSelector?: string;
+ /** The duration in seconds before the object should be deleted. Value must be non-negative integer. The value zero indicates delete immediately. If this value is nil, the default grace period for the specified type will be used. Defaults to a per object value if not specified. zero means delete immediately. */
+ gracePeriodSeconds?: number;
+ /** if set to true, it will trigger an unsafe deletion of the resource in case the normal deletion flow fails with a corrupt object error. A resource is considered corrupt if it can not be retrieved from the underlying storage successfully because of a) its data can not be transformed e.g. decryption failure, or b) it fails to decode into an object. NOTE: unsafe deletion ignores finalizer constraints, skips precondition checks, and removes the object from the storage. WARNING: This may potentially break the cluster if the workload associated with the resource being unsafe-deleted relies on normal deletion flow. Use only if you REALLY know what you are doing. The default value is false, and the user must opt in to enable it */
+ ignoreStoreReadErrorWithClusterBreakingPotential?: boolean;
+ /** A selector to restrict the list of returned objects by their labels. Defaults to everything. */
+ labelSelector?: string;
+ /** limit is a maximum number of responses to return for a list call. If more items exist, the server will set the `continue` field on the list metadata to a value that can be used with the same initial query to retrieve the next set of results. Setting a limit may return fewer than the requested amount of items (up to zero items) in the event all requested objects are filtered out and clients should only use the presence of the continue field to determine whether more results are available. Servers may choose not to support the limit argument and will return all of the available results. If limit is specified and the continue field is empty, clients may assume that no more results are available. This field is not supported if watch is true.
+
+ The server guarantees that the objects returned when using continue will be identical to issuing a single list call without a limit - that is, no objects created, modified, or deleted after the first request is issued will be included in any subsequent continued requests. This is sometimes referred to as a consistent snapshot, and ensures that a client that is using limit to receive smaller chunks of a very large result can ensure they see all possible objects. If objects are updated during a chunked list the version of the object that was present at the time the first list result was calculated is returned. */
+ limit?: number;
+ /** Deprecated: please use the PropagationPolicy, this field will be deprecated in 1.7. Should the dependent objects be orphaned. If true/false, the "orphan" finalizer will be added to/removed from the object's finalizers list. Either this field or PropagationPolicy may be set, but not both. */
+ orphanDependents?: boolean;
+ /** Whether and how garbage collection will be performed. Either this field or OrphanDependents may be set, but not both. The default policy is decided by the existing finalizer set in the metadata.finalizers and the resource-specific default policy. Acceptable values are: 'Orphan' - orphan the dependents; 'Background' - allow the garbage collector to delete the dependents in the background; 'Foreground' - a cascading policy that deletes all dependents in the foreground. */
+ propagationPolicy?: string;
+ /** resourceVersion sets a constraint on what resource versions a request may be served from. See https://kubernetes.io/docs/reference/using-api/api-concepts/#resource-versions for details.
+
+ Defaults to unset */
+ resourceVersion?: string;
+ /** resourceVersionMatch determines how resourceVersion is applied to list calls. It is highly recommended that resourceVersionMatch be set for list calls where resourceVersion is set See https://kubernetes.io/docs/reference/using-api/api-concepts/#resource-versions for details.
+
+ Defaults to unset */
+ resourceVersionMatch?: string;
+ /** `sendInitialEvents=true` may be set together with `watch=true`. In that case, the watch stream will begin with synthetic events to produce the current state of objects in the collection. Once all such events have been sent, a synthetic "Bookmark" event will be sent. The bookmark will report the ResourceVersion (RV) corresponding to the set of objects, and be marked with `"k8s.io/initial-events-end": "true"` annotation. Afterwards, the watch stream will proceed as usual, sending watch events corresponding to changes (subsequent to the RV) to objects watched.
+
+ When `sendInitialEvents` option is set, we require `resourceVersionMatch` option to also be set. The semantic of the watch request is as following: - `resourceVersionMatch` = NotOlderThan
+ is interpreted as "data at least as new as the provided `resourceVersion`"
+ and the bookmark event is send when the state is synced
+ to a `resourceVersion` at least as fresh as the one provided by the ListOptions.
+ If `resourceVersion` is unset, this is interpreted as "consistent read" and the
+ bookmark event is send when the state is synced at least to the moment
+ when request started being processed.
+ - `resourceVersionMatch` set to any other value or unset
+ Invalid error is returned.
+
+ Defaults to true if `resourceVersion=""` or `resourceVersion="0"` (for backward compatibility reasons) and to false otherwise. */
+ sendInitialEvents?: boolean;
+ /** Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity. */
+ timeoutSeconds?: number;
+ deleteOptions: DeleteOptions;
+};
export type GetRepositoryResponse = /** status 200 OK */ Repository;
export type GetRepositoryArg = {
/** name of the Repository */
@@ -1099,6 +1175,7 @@ export const {
useGetJobQuery,
useListRepositoryQuery,
useCreateRepositoryMutation,
+ useDeletecollectionRepositoryMutation,
useGetRepositoryQuery,
useReplaceRepositoryMutation,
useDeleteRepositoryMutation,
diff --git a/public/app/features/provisioning/constants.ts b/public/app/features/provisioning/constants.ts
index 5ac108f6c9e..bf0b89641e1 100644
--- a/public/app/features/provisioning/constants.ts
+++ b/public/app/features/provisioning/constants.ts
@@ -1,2 +1,3 @@
export const PROVISIONING_URL = '/admin/provisioning';
export const NEW_URL = `${PROVISIONING_URL}/new`;
+export const MIGRATE_URL = `${PROVISIONING_URL}/migrate`;
diff --git a/public/app/features/provisioning/utils/routes.ts b/public/app/features/provisioning/utils/routes.ts
index 821dee51171..64d6aa32895 100644
--- a/public/app/features/provisioning/utils/routes.ts
+++ b/public/app/features/provisioning/utils/routes.ts
@@ -25,9 +25,12 @@ export function getProvisioningRoutes(): RouteDescriptor[] {
),
},
{
- path: PROVISIONING_URL + '/setup',
+ path: PROVISIONING_URL + '/migrate',
component: SafeDynamicImport(
- () => import(/* webpackChunkName: "ProvisioningWizardPage"*/ 'app/features/provisioning/Wizard/WizardPage')
+ () =>
+ import(
+ /* webpackChunkName: "ProvisioningWizardPage"*/ 'app/features/provisioning/Wizard/MigrateToProvisioningPage'
+ )
),
},
{
diff --git a/public/img/provisioning-empty.svg b/public/img/provisioning-empty.svg
new file mode 100644
index 00000000000..5da0c428fd3
--- /dev/null
+++ b/public/img/provisioning-empty.svg
@@ -0,0 +1,17 @@
+
diff --git a/scripts/generate-rtk-apis.ts b/scripts/generate-rtk-apis.ts
index 1badc7b0863..106f6f9f112 100644
--- a/scripts/generate-rtk-apis.ts
+++ b/scripts/generate-rtk-apis.ts
@@ -65,8 +65,6 @@ function filterEndpoints(name: string) {
return (
!name.toLowerCase().includes('forallnamespaces') &&
!name.toLowerCase().includes('getapiresources') &&
- !name.toLowerCase().includes('watch') &&
- !name.toLowerCase().includes('collection') &&
!name.toLowerCase().includes('update')
);
}