Compare commits
10 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 7e256253b8 | |||
| ff9b3722ec | |||
| 8bad33de4c | |||
| 8506a89579 | |||
| f34890327f | |||
| cf696db273 | |||
| 8b4a040dc8 | |||
| 18c587a460 | |||
| 36a7c10f67 | |||
| de1405b1e3 |
@@ -543,6 +543,7 @@ i18next.config.ts @grafana/grafana-frontend-platform
|
||||
/packages/grafana-data/tsconfig.json @grafana/grafana-frontend-platform
|
||||
/packages/grafana-data/test/ @grafana/grafana-frontend-platform
|
||||
/packages/grafana-data/typings/ @grafana/grafana-frontend-platform
|
||||
/packages/grafana-data/scripts/ @grafana/grafana-frontend-platform
|
||||
|
||||
/packages/grafana-data/src/**/*logs* @grafana/observability-logs
|
||||
/packages/grafana-data/src/context/plugins/ @grafana/plugins-platform-frontend
|
||||
|
||||
@@ -35,6 +35,14 @@
|
||||
},
|
||||
"./test": {
|
||||
"@grafana-app/source": "./test/index.ts"
|
||||
},
|
||||
"./themes/schema.generated.json": {
|
||||
"@grafana-app/source": "./src/themes/schema.generated.json",
|
||||
"default": "./dist/esm/themes/schema.generated.json"
|
||||
},
|
||||
"./themes/definitions/*.json": {
|
||||
"@grafana-app/source": "./src/themes/themeDefinitions/*.json",
|
||||
"default": "./dist/esm/themes/themeDefinitions/*.json"
|
||||
}
|
||||
},
|
||||
"publishConfig": {
|
||||
@@ -52,7 +60,7 @@
|
||||
"typecheck": "tsc --emitDeclarationOnly false --noEmit",
|
||||
"prepack": "cp package.json package.json.bak && node ../../scripts/prepare-npm-package.js",
|
||||
"postpack": "mv package.json.bak package.json",
|
||||
"themes-schema": "tsx ./src/themes/scripts/generateSchema.ts"
|
||||
"themes-schema": "tsx ./scripts/generateSchema.ts"
|
||||
},
|
||||
"dependencies": {
|
||||
"@braintree/sanitize-url": "7.0.1",
|
||||
@@ -102,6 +110,7 @@
|
||||
"react-dom": "18.3.1",
|
||||
"rimraf": "6.0.1",
|
||||
"rollup": "^4.22.4",
|
||||
"rollup-plugin-copy": "3.5.0",
|
||||
"rollup-plugin-esbuild": "6.2.1",
|
||||
"rollup-plugin-node-externals": "^8.0.0",
|
||||
"tsx": "^4.21.0",
|
||||
|
||||
@@ -1,21 +1,40 @@
|
||||
import json from '@rollup/plugin-json';
|
||||
import { createRequire } from 'node:module';
|
||||
import copy from 'rollup-plugin-copy';
|
||||
|
||||
import { entryPoint, plugins, esmOutput, cjsOutput } from '../rollup.config.parts';
|
||||
|
||||
const rq = createRequire(import.meta.url);
|
||||
const pkg = rq('./package.json');
|
||||
|
||||
const grafanaDataPlugins = [
|
||||
...plugins,
|
||||
copy({
|
||||
targets: [
|
||||
{
|
||||
src: 'src/themes/schema.generated.json',
|
||||
dest: 'dist/esm/',
|
||||
},
|
||||
{
|
||||
src: 'src/themes/themeDefinitions/*.json',
|
||||
dest: 'dist/esm/',
|
||||
},
|
||||
],
|
||||
flatten: false,
|
||||
}),
|
||||
json(),
|
||||
];
|
||||
|
||||
export default [
|
||||
{
|
||||
input: entryPoint,
|
||||
plugins: [...plugins, json()],
|
||||
plugins: grafanaDataPlugins,
|
||||
output: [cjsOutput(pkg, 'grafana-data'), esmOutput(pkg, 'grafana-data')],
|
||||
treeshake: false,
|
||||
},
|
||||
{
|
||||
input: 'src/unstable.ts',
|
||||
plugins: [...plugins, json()],
|
||||
plugins: grafanaDataPlugins,
|
||||
output: [cjsOutput(pkg, 'grafana-data'), esmOutput(pkg, 'grafana-data')],
|
||||
treeshake: false,
|
||||
},
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
import fs from 'node:fs';
|
||||
import path from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
|
||||
import { NewThemeOptionsSchema } from '../src/themes/createTheme';
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = path.dirname(__filename);
|
||||
const jsonOut = path.join(__dirname, '..', 'src', 'themes', 'schema.generated.json');
|
||||
|
||||
fs.writeFileSync(
|
||||
jsonOut,
|
||||
JSON.stringify(
|
||||
NewThemeOptionsSchema.toJSONSchema({
|
||||
target: 'draft-07',
|
||||
}),
|
||||
undefined,
|
||||
2
|
||||
)
|
||||
);
|
||||
|
||||
console.log('Successfully generated theme schema');
|
||||
@@ -93,7 +93,6 @@ export { DataTransformerID } from '../transformations/transformers/ids';
|
||||
|
||||
export { mergeTransformer } from '../transformations/transformers/merge';
|
||||
export { getThemeById } from '../themes/registry';
|
||||
export * as experimentalThemeDefinitions from '../themes/themeDefinitions';
|
||||
export { GrafanaEdition } from '../types/config';
|
||||
export { SIPrefix } from '../valueFormats/symbolFormatters';
|
||||
|
||||
|
||||
@@ -1,7 +1,18 @@
|
||||
import { Registry, RegistryItem } from '../utils/Registry';
|
||||
|
||||
import { createTheme, NewThemeOptionsSchema } from './createTheme';
|
||||
import * as extraThemes from './themeDefinitions';
|
||||
import aubergine from './themeDefinitions/aubergine.json';
|
||||
import debug from './themeDefinitions/debug.json';
|
||||
import desertbloom from './themeDefinitions/desertbloom.json';
|
||||
import gildedgrove from './themeDefinitions/gildedgrove.json';
|
||||
import gloom from './themeDefinitions/gloom.json';
|
||||
import mars from './themeDefinitions/mars.json';
|
||||
import matrix from './themeDefinitions/matrix.json';
|
||||
import sapphiredusk from './themeDefinitions/sapphiredusk.json';
|
||||
import synthwave from './themeDefinitions/synthwave.json';
|
||||
import tron from './themeDefinitions/tron.json';
|
||||
import victorian from './themeDefinitions/victorian.json';
|
||||
import zen from './themeDefinitions/zen.json';
|
||||
import { GrafanaTheme2 } from './types';
|
||||
|
||||
export interface ThemeRegistryItem extends RegistryItem {
|
||||
@@ -9,6 +20,21 @@ export interface ThemeRegistryItem extends RegistryItem {
|
||||
build: () => GrafanaTheme2;
|
||||
}
|
||||
|
||||
const extraThemes: { [key: string]: unknown } = {
|
||||
aubergine,
|
||||
debug,
|
||||
desertbloom,
|
||||
gildedgrove,
|
||||
gloom,
|
||||
mars,
|
||||
matrix,
|
||||
sapphiredusk,
|
||||
synthwave,
|
||||
tron,
|
||||
victorian,
|
||||
zen,
|
||||
};
|
||||
|
||||
/**
|
||||
* @internal
|
||||
* Only for internal use, never use this from a plugin
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import { fileURLToPath } from 'url';
|
||||
|
||||
import { NewThemeOptionsSchema } from '../createTheme';
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = path.dirname(__filename);
|
||||
|
||||
fs.writeFileSync(
|
||||
path.join(__dirname, '../schema.generated.json'),
|
||||
JSON.stringify(
|
||||
NewThemeOptionsSchema.toJSONSchema({
|
||||
target: 'draft-07',
|
||||
}),
|
||||
undefined,
|
||||
2
|
||||
)
|
||||
);
|
||||
@@ -1,12 +0,0 @@
|
||||
export { default as aubergine } from './aubergine.json';
|
||||
export { default as debug } from './debug.json';
|
||||
export { default as desertbloom } from './desertbloom.json';
|
||||
export { default as gildedgrove } from './gildedgrove.json';
|
||||
export { default as mars } from './mars.json';
|
||||
export { default as matrix } from './matrix.json';
|
||||
export { default as sapphiredusk } from './sapphiredusk.json';
|
||||
export { default as synthwave } from './synthwave.json';
|
||||
export { default as tron } from './tron.json';
|
||||
export { default as victorian } from './victorian.json';
|
||||
export { default as zen } from './zen.json';
|
||||
export { default as gloom } from './gloom.json';
|
||||
@@ -9,4 +9,4 @@
|
||||
* and be subject to the standard policies
|
||||
*/
|
||||
|
||||
export { default as themeJsonSchema } from './themes/schema.generated.json';
|
||||
export {};
|
||||
|
||||
@@ -8,7 +8,8 @@
|
||||
"emitDeclarationOnly": true,
|
||||
"isolatedModules": true,
|
||||
"rootDirs": ["."],
|
||||
"moduleResolution": "bundler"
|
||||
"moduleResolution": "bundler",
|
||||
"resolveJsonModule": true
|
||||
},
|
||||
"exclude": ["dist/**/*"],
|
||||
"include": [
|
||||
|
||||
@@ -8,7 +8,6 @@ import (
|
||||
"k8s.io/apiserver/pkg/authorization/authorizer"
|
||||
|
||||
iamv0 "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1"
|
||||
"github.com/grafana/grafana/pkg/apimachinery/identity"
|
||||
"github.com/grafana/grafana/pkg/apimachinery/utils"
|
||||
iamauthorizer "github.com/grafana/grafana/pkg/registry/apis/iam/authorizer"
|
||||
"github.com/grafana/grafana/pkg/registry/apis/iam/legacy"
|
||||
@@ -41,22 +40,6 @@ func newIAMAuthorizer(
|
||||
return authorizer.DecisionAllow, "", nil
|
||||
})
|
||||
|
||||
serviceIdentityAuthorizer := authorizer.AuthorizerFunc(func(
|
||||
ctx context.Context, attr authorizer.Attributes,
|
||||
) (authorized authorizer.Decision, reason string, err error) {
|
||||
if identity.IsServiceIdentity(ctx) {
|
||||
// A Grafana sub-system should have full access. We trust them to make wise decisions.
|
||||
return authorizer.DecisionAllow, "", nil
|
||||
}
|
||||
|
||||
req, err := identity.GetRequester(ctx)
|
||||
if err == nil && req != nil && req.GetIsGrafanaAdmin() {
|
||||
return authorizer.DecisionAllow, "", nil
|
||||
}
|
||||
|
||||
return authorizer.DecisionDeny, "", nil
|
||||
})
|
||||
|
||||
// Identity specific resources
|
||||
legacyAuthorizer := gfauthorizer.NewResourceAuthorizer(legacyAccessClient)
|
||||
resourceAuthorizer["display"] = legacyAuthorizer
|
||||
@@ -74,8 +57,6 @@ func newIAMAuthorizer(
|
||||
resourceAuthorizer[iamv0.TeamBindingResourceInfo.GetName()] = allowAuthorizer
|
||||
resourceAuthorizer["searchUsers"] = serviceAuthorizer
|
||||
resourceAuthorizer["searchTeams"] = serviceAuthorizer
|
||||
// TODO: Implement fine-grained authorization for external group mapping search on the search level
|
||||
resourceAuthorizer["searchExternalGroupMappings"] = serviceIdentityAuthorizer
|
||||
|
||||
return &iamAuthorizer{resourceAuthorizer: resourceAuthorizer}
|
||||
}
|
||||
|
||||
@@ -97,8 +97,8 @@ func (r *TeamBindingAuthorizer) beforeWrite(ctx context.Context, obj runtime.Obj
|
||||
teamName := concreteObj.Spec.TeamRef.Name
|
||||
checkReq := types.CheckRequest{
|
||||
Namespace: authInfo.GetNamespace(),
|
||||
Group: iamv0.TeamResourceInfo.GroupResource().Group,
|
||||
Resource: iamv0.TeamResourceInfo.GroupResource().Resource,
|
||||
Group: iamv0.GROUP,
|
||||
Resource: iamv0.TeamResourceInfo.GetName(),
|
||||
Verb: utils.VerbSetPermissions,
|
||||
Name: teamName,
|
||||
}
|
||||
|
||||
@@ -1,10 +1,6 @@
|
||||
package externalgroupmapping
|
||||
|
||||
import (
|
||||
"github.com/grafana/grafana/pkg/services/apiserver/builder"
|
||||
"k8s.io/apiserver/pkg/registry/rest"
|
||||
"k8s.io/kube-openapi/pkg/common"
|
||||
)
|
||||
import "k8s.io/apiserver/pkg/registry/rest"
|
||||
|
||||
type TeamGroupsHandler interface {
|
||||
rest.Storage
|
||||
@@ -12,7 +8,3 @@ type TeamGroupsHandler interface {
|
||||
rest.StorageMetadata
|
||||
rest.Connecter
|
||||
}
|
||||
|
||||
type SearchHandler interface {
|
||||
GetAPIRoutes(defs map[string]common.OpenAPIDefinition) *builder.APIRoutes
|
||||
}
|
||||
|
||||
@@ -1,167 +0,0 @@
|
||||
package externalgroupmapping
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
iamv0 "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1"
|
||||
"github.com/grafana/grafana/pkg/services/apiserver/builder"
|
||||
"github.com/grafana/grafana/pkg/util/errhttp"
|
||||
"k8s.io/apimachinery/pkg/api/errors"
|
||||
"k8s.io/kube-openapi/pkg/common"
|
||||
"k8s.io/kube-openapi/pkg/spec3"
|
||||
"k8s.io/kube-openapi/pkg/validation/spec"
|
||||
)
|
||||
|
||||
var _ SearchHandler = (*NoopSearchREST)(nil)
|
||||
|
||||
type NoopSearchREST struct{}
|
||||
|
||||
func ProvideNoopSearchREST() *NoopSearchREST {
|
||||
return &NoopSearchREST{}
|
||||
}
|
||||
|
||||
func (n *NoopSearchREST) GetAPIRoutes(defs map[string]common.OpenAPIDefinition) *builder.APIRoutes {
|
||||
searchResults := defs["github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.ExternalGroupMappingList"].Schema
|
||||
return &builder.APIRoutes{
|
||||
Namespace: []builder.APIRouteHandler{
|
||||
{
|
||||
Path: "searchExternalGroupMappings",
|
||||
Spec: &spec3.PathProps{
|
||||
Post: &spec3.Operation{
|
||||
OperationProps: spec3.OperationProps{
|
||||
Description: "External Group Mapping search",
|
||||
Tags: []string{"Search"},
|
||||
OperationId: "searchExternalGroupMappings",
|
||||
RequestBody: &spec3.RequestBody{
|
||||
RequestBodyProps: spec3.RequestBodyProps{
|
||||
Content: map[string]*spec3.MediaType{
|
||||
"application/json": {
|
||||
MediaTypeProps: spec3.MediaTypeProps{
|
||||
Schema: &spec.Schema{
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Type: []string{"object"},
|
||||
Properties: map[string]spec.Schema{
|
||||
"externalGroups": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Type: []string{"array"},
|
||||
Items: &spec.SchemaOrArray{
|
||||
Schema: &spec.Schema{
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Type: []string{"string"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Parameters: []*spec3.Parameter{
|
||||
{
|
||||
ParameterProps: spec3.ParameterProps{
|
||||
Name: "namespace",
|
||||
In: "path",
|
||||
Required: true,
|
||||
Example: "default",
|
||||
Description: "workspace",
|
||||
Schema: spec.StringProperty(),
|
||||
},
|
||||
},
|
||||
{
|
||||
ParameterProps: spec3.ParameterProps{
|
||||
Name: "teamName",
|
||||
In: "query",
|
||||
Required: false,
|
||||
Description: "Team name",
|
||||
Schema: spec.StringProperty(),
|
||||
},
|
||||
},
|
||||
{
|
||||
ParameterProps: spec3.ParameterProps{
|
||||
Name: "limit",
|
||||
In: "query",
|
||||
Description: "number of results to return",
|
||||
Example: 30,
|
||||
Required: false,
|
||||
Schema: spec.Int64Property(),
|
||||
},
|
||||
},
|
||||
{
|
||||
ParameterProps: spec3.ParameterProps{
|
||||
Name: "page",
|
||||
In: "query",
|
||||
Description: "page number (starting from 1)",
|
||||
Example: 1,
|
||||
Required: false,
|
||||
Schema: spec.Int64Property(),
|
||||
},
|
||||
},
|
||||
{
|
||||
ParameterProps: spec3.ParameterProps{
|
||||
Name: "offset",
|
||||
In: "query",
|
||||
Description: "number of results to skip",
|
||||
Example: 0,
|
||||
Required: false,
|
||||
Schema: spec.Int64Property(),
|
||||
},
|
||||
},
|
||||
{
|
||||
ParameterProps: spec3.ParameterProps{
|
||||
Name: "sort",
|
||||
In: "query",
|
||||
Description: "sortable field",
|
||||
Example: "",
|
||||
Examples: map[string]*spec3.Example{
|
||||
"externalGroup": {
|
||||
ExampleProps: spec3.ExampleProps{
|
||||
Summary: "externalGroup ascending",
|
||||
Value: "externalGroup",
|
||||
},
|
||||
},
|
||||
"-externalGroup": {
|
||||
ExampleProps: spec3.ExampleProps{
|
||||
Summary: "externalGroup descending",
|
||||
Value: "-externalGroup",
|
||||
},
|
||||
},
|
||||
},
|
||||
Required: false,
|
||||
Schema: spec.StringProperty(),
|
||||
},
|
||||
},
|
||||
},
|
||||
Responses: &spec3.Responses{
|
||||
ResponsesProps: spec3.ResponsesProps{
|
||||
Default: &spec3.Response{
|
||||
ResponseProps: spec3.ResponseProps{
|
||||
Description: "Default OK response",
|
||||
Content: map[string]*spec3.MediaType{
|
||||
"application/json": {
|
||||
MediaTypeProps: spec3.MediaTypeProps{
|
||||
Schema: &searchResults,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Handler: n.doSearch,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (n *NoopSearchREST) doSearch(w http.ResponseWriter, r *http.Request) {
|
||||
errhttp.Write(r.Context(), errors.NewForbidden(iamv0.ExternalGroupMappingResourceInfo.GroupResource(), "", fmt.Errorf("functionality not available")), w)
|
||||
}
|
||||
@@ -82,12 +82,11 @@ type IdentityAccessManagementAPIBuilder struct {
|
||||
reg prometheus.Registerer
|
||||
logger log.Logger
|
||||
|
||||
dual dualwrite.Service
|
||||
unified resource.ResourceClient
|
||||
userSearchClient resourcepb.ResourceIndexClient
|
||||
userSearchHandler *user.SearchHandler
|
||||
teamSearch *TeamSearchHandler
|
||||
externalGroupMappingSearchHandler externalgroupmapping.SearchHandler
|
||||
dual dualwrite.Service
|
||||
unified resource.ResourceClient
|
||||
userSearchClient resourcepb.ResourceIndexClient
|
||||
userSearchHandler *user.SearchHandler
|
||||
teamSearch *TeamSearchHandler
|
||||
|
||||
teamGroupsHandler externalgroupmapping.TeamGroupsHandler
|
||||
|
||||
|
||||
@@ -79,7 +79,6 @@ func RegisterAPIService(
|
||||
roleBindingsStorage RoleBindingStorageBackend,
|
||||
externalGroupMappingStorageBackend ExternalGroupMappingStorageBackend,
|
||||
teamGroupsHandlerImpl externalgroupmapping.TeamGroupsHandler,
|
||||
externalGroupMappingSearchHandler externalgroupmapping.SearchHandler,
|
||||
dual dualwrite.Service,
|
||||
unified resource.ResourceClient,
|
||||
orgService org.Service,
|
||||
@@ -102,32 +101,31 @@ func RegisterAPIService(
|
||||
)
|
||||
|
||||
builder := &IdentityAccessManagementAPIBuilder{
|
||||
store: store,
|
||||
userLegacyStore: user.NewLegacyStore(store, accessClient, enableAuthnMutation, tracing),
|
||||
saLegacyStore: serviceaccount.NewLegacyStore(store, accessClient, enableAuthnMutation, tracing),
|
||||
legacyTeamStore: team.NewLegacyStore(store, accessClient, enableAuthnMutation, tracing),
|
||||
teamBindingLegacyStore: teambinding.NewLegacyBindingStore(store, enableAuthnMutation, tracing),
|
||||
ssoLegacyStore: sso.NewLegacyStore(ssoService, tracing),
|
||||
coreRolesStorage: coreRolesStorage,
|
||||
roleApiInstaller: roleApiInstaller,
|
||||
resourcePermissionsStorage: resourcepermission.ProvideStorageBackend(dbProvider),
|
||||
roleBindingsStorage: roleBindingsStorage,
|
||||
externalGroupMappingStorage: externalGroupMappingStorageBackend,
|
||||
teamGroupsHandler: teamGroupsHandlerImpl,
|
||||
externalGroupMappingSearchHandler: externalGroupMappingSearchHandler,
|
||||
sso: ssoService,
|
||||
resourceParentProvider: resourceParentProvider,
|
||||
authorizer: authorizer,
|
||||
legacyAccessClient: legacyAccessClient,
|
||||
accessClient: accessClient,
|
||||
zClient: zClient,
|
||||
zTickets: make(chan bool, MaxConcurrentZanzanaWrites),
|
||||
display: user.NewLegacyDisplayREST(store),
|
||||
reg: reg,
|
||||
logger: log.New("iam.apis"),
|
||||
features: features,
|
||||
dual: dual,
|
||||
unified: unified,
|
||||
store: store,
|
||||
userLegacyStore: user.NewLegacyStore(store, accessClient, enableAuthnMutation, tracing),
|
||||
saLegacyStore: serviceaccount.NewLegacyStore(store, accessClient, enableAuthnMutation, tracing),
|
||||
legacyTeamStore: team.NewLegacyStore(store, accessClient, enableAuthnMutation, tracing),
|
||||
teamBindingLegacyStore: teambinding.NewLegacyBindingStore(store, enableAuthnMutation, tracing),
|
||||
ssoLegacyStore: sso.NewLegacyStore(ssoService, tracing),
|
||||
coreRolesStorage: coreRolesStorage,
|
||||
roleApiInstaller: roleApiInstaller,
|
||||
resourcePermissionsStorage: resourcepermission.ProvideStorageBackend(dbProvider),
|
||||
roleBindingsStorage: roleBindingsStorage,
|
||||
externalGroupMappingStorage: externalGroupMappingStorageBackend,
|
||||
teamGroupsHandler: teamGroupsHandlerImpl,
|
||||
sso: ssoService,
|
||||
resourceParentProvider: resourceParentProvider,
|
||||
authorizer: authorizer,
|
||||
legacyAccessClient: legacyAccessClient,
|
||||
accessClient: accessClient,
|
||||
zClient: zClient,
|
||||
zTickets: make(chan bool, MaxConcurrentZanzanaWrites),
|
||||
display: user.NewLegacyDisplayREST(store),
|
||||
reg: reg,
|
||||
logger: log.New("iam.apis"),
|
||||
features: features,
|
||||
dual: dual,
|
||||
unified: unified,
|
||||
userSearchClient: resource.NewSearchClient(dualwrite.NewSearchAdapter(dual), iamv0.UserResourceInfo.GroupResource(),
|
||||
unified, user.NewUserLegacySearchClient(orgService, tracing, cfg), features),
|
||||
teamSearch: NewTeamSearchHandler(tracing, dual, team.NewLegacyTeamSearchClient(teamService), unified, features),
|
||||
@@ -627,7 +625,7 @@ func (b *IdentityAccessManagementAPIBuilder) PostProcessOpenAPI(oas *spec3.OpenA
|
||||
func (b *IdentityAccessManagementAPIBuilder) GetAPIRoutes(gv schema.GroupVersion) *builder.APIRoutes {
|
||||
defs := b.GetOpenAPIDefinitions()(func(path string) spec.Ref { return spec.Ref{} })
|
||||
|
||||
searchRoutes := make([]*builder.APIRoutes, 0, 3)
|
||||
searchRoutes := make([]*builder.APIRoutes, 0, 2)
|
||||
if b.userSearchHandler != nil {
|
||||
searchRoutes = append(searchRoutes, b.userSearchHandler.GetAPIRoutes(defs))
|
||||
}
|
||||
@@ -636,10 +634,6 @@ func (b *IdentityAccessManagementAPIBuilder) GetAPIRoutes(gv schema.GroupVersion
|
||||
searchRoutes = append(searchRoutes, b.teamSearch.GetAPIRoutes(defs))
|
||||
}
|
||||
|
||||
if b.externalGroupMappingSearchHandler != nil {
|
||||
searchRoutes = append(searchRoutes, b.externalGroupMappingSearchHandler.GetAPIRoutes(defs))
|
||||
}
|
||||
|
||||
routes := []*builder.APIRoutes{b.display.GetAPIRoutes(defs)}
|
||||
routes = append(routes, searchRoutes...)
|
||||
return mergeAPIRoutes(routes...)
|
||||
|
||||
@@ -35,9 +35,6 @@ var WireSetExts = wire.NewSet(
|
||||
externalgroupmapping.ProvideNoopTeamGroupsREST,
|
||||
wire.Bind(new(externalgroupmapping.TeamGroupsHandler), new(*externalgroupmapping.NoopTeamGroupsREST)),
|
||||
|
||||
externalgroupmapping.ProvideNoopSearchREST,
|
||||
wire.Bind(new(externalgroupmapping.SearchHandler), new(*externalgroupmapping.NoopSearchREST)),
|
||||
|
||||
// Auditing Options
|
||||
auditing.ProvideNoopBackend,
|
||||
auditing.ProvideNoopPolicyRuleProvider,
|
||||
|
||||
Generated
+2
-4
@@ -883,8 +883,7 @@ func Initialize(ctx context.Context, cfg *setting.Cfg, opts Options, apiOpts api
|
||||
storageBackendImpl := noopstorage.ProvideStorageBackend()
|
||||
roleApiInstaller := iam.ProvideNoopRoleApiInstaller()
|
||||
noopTeamGroupsREST := externalgroupmapping.ProvideNoopTeamGroupsREST()
|
||||
noopSearchREST := externalgroupmapping.ProvideNoopSearchREST()
|
||||
identityAccessManagementAPIBuilder, err := iam.RegisterAPIService(cfg, featureToggles, apiserverService, ssosettingsimplService, sqlStore, accessControl, accessClient, zanzanaClient, registerer, storageBackendImpl, roleApiInstaller, tracingService, storageBackendImpl, storageBackendImpl, noopTeamGroupsREST, noopSearchREST, dualwriteService, resourceClient, orgService, userService, teamService, eventualRestConfigProvider)
|
||||
identityAccessManagementAPIBuilder, err := iam.RegisterAPIService(cfg, featureToggles, apiserverService, ssosettingsimplService, sqlStore, accessControl, accessClient, zanzanaClient, registerer, storageBackendImpl, roleApiInstaller, tracingService, storageBackendImpl, storageBackendImpl, noopTeamGroupsREST, dualwriteService, resourceClient, orgService, userService, teamService, eventualRestConfigProvider)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -1552,8 +1551,7 @@ func InitializeForTest(ctx context.Context, t sqlutil.ITestDB, testingT interfac
|
||||
storageBackendImpl := noopstorage.ProvideStorageBackend()
|
||||
roleApiInstaller := iam.ProvideNoopRoleApiInstaller()
|
||||
noopTeamGroupsREST := externalgroupmapping.ProvideNoopTeamGroupsREST()
|
||||
noopSearchREST := externalgroupmapping.ProvideNoopSearchREST()
|
||||
identityAccessManagementAPIBuilder, err := iam.RegisterAPIService(cfg, featureToggles, apiserverService, ssosettingsimplService, sqlStore, accessControl, accessClient, zanzanaClient, registerer, storageBackendImpl, roleApiInstaller, tracingService, storageBackendImpl, storageBackendImpl, noopTeamGroupsREST, noopSearchREST, dualwriteService, resourceClient, orgService, userService, teamService, eventualRestConfigProvider)
|
||||
identityAccessManagementAPIBuilder, err := iam.RegisterAPIService(cfg, featureToggles, apiserverService, ssosettingsimplService, sqlStore, accessControl, accessClient, zanzanaClient, registerer, storageBackendImpl, roleApiInstaller, tracingService, storageBackendImpl, storageBackendImpl, noopTeamGroupsREST, dualwriteService, resourceClient, orgService, userService, teamService, eventualRestConfigProvider)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -912,139 +912,6 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"/apis/iam.grafana.app/v0alpha1/namespaces/{namespace}/searchExternalGroupMappings": {
|
||||
"post": {
|
||||
"tags": [
|
||||
"Search"
|
||||
],
|
||||
"description": "External Group Mapping search",
|
||||
"operationId": "searchExternalGroupMappings",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "namespace",
|
||||
"in": "path",
|
||||
"description": "workspace",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string"
|
||||
},
|
||||
"example": "default"
|
||||
},
|
||||
{
|
||||
"name": "teamName",
|
||||
"in": "query",
|
||||
"description": "Team name",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "limit",
|
||||
"in": "query",
|
||||
"description": "number of results to return",
|
||||
"schema": {
|
||||
"type": "integer",
|
||||
"format": "int64"
|
||||
},
|
||||
"example": 30
|
||||
},
|
||||
{
|
||||
"name": "page",
|
||||
"in": "query",
|
||||
"description": "page number (starting from 1)",
|
||||
"schema": {
|
||||
"type": "integer",
|
||||
"format": "int64"
|
||||
},
|
||||
"example": 1
|
||||
},
|
||||
{
|
||||
"name": "offset",
|
||||
"in": "query",
|
||||
"description": "number of results to skip",
|
||||
"schema": {
|
||||
"type": "integer",
|
||||
"format": "int64"
|
||||
},
|
||||
"example": 0
|
||||
},
|
||||
{
|
||||
"name": "sort",
|
||||
"in": "query",
|
||||
"description": "sortable field",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
},
|
||||
"examples": {
|
||||
"": {
|
||||
"summary": "default sorting",
|
||||
"value": "externalGroup"
|
||||
},
|
||||
"-externalGroup": {
|
||||
"summary": "externalGroup descending",
|
||||
"value": "-externalGroup"
|
||||
},
|
||||
"externalGroup": {
|
||||
"summary": "externalGroup ascending",
|
||||
"value": "externalGroup"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"requestBody": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"externalGroups": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"responses": {
|
||||
"default": {
|
||||
"description": "Default OK response",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"metadata",
|
||||
"items"
|
||||
],
|
||||
"properties": {
|
||||
"apiVersion": {
|
||||
"description": "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources",
|
||||
"type": "string"
|
||||
},
|
||||
"items": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"default": {}
|
||||
}
|
||||
},
|
||||
"kind": {
|
||||
"description": "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds",
|
||||
"type": "string"
|
||||
},
|
||||
"metadata": {
|
||||
"default": {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/apis/iam.grafana.app/v0alpha1/namespaces/{namespace}/searchTeams": {
|
||||
"get": {
|
||||
"tags": [
|
||||
|
||||
@@ -19,6 +19,7 @@ export interface RevisionsModel {
|
||||
data: Dashboard;
|
||||
}
|
||||
|
||||
// TODO: this should be removed entirely
|
||||
export class HistorySrv {
|
||||
getHistoryList(dashboardUID: string, options: HistoryListOpts) {
|
||||
if (typeof dashboardUID !== 'string') {
|
||||
@@ -35,16 +36,6 @@ export class HistorySrv {
|
||||
|
||||
return getBackendSrv().get(`api/dashboards/uid/${dashboardUID}/versions/${version}`);
|
||||
}
|
||||
|
||||
restoreDashboard(dashboardUID: string, version: number) {
|
||||
if (typeof dashboardUID !== 'string') {
|
||||
return Promise.resolve({});
|
||||
}
|
||||
|
||||
const url = `api/dashboards/uid/${dashboardUID}/restore`;
|
||||
|
||||
return getBackendSrv().post(url, { version });
|
||||
}
|
||||
}
|
||||
|
||||
const historySrv = new HistorySrv();
|
||||
|
||||
@@ -56,6 +56,24 @@ export class UnifiedDashboardAPI
|
||||
return await this.v1Client.deleteDashboard(uid, showSuccessAlert);
|
||||
}
|
||||
|
||||
async listDashboardHistory(uid: string) {
|
||||
const v1Response = await this.v1Client.listDashboardHistory(uid);
|
||||
const filteredV1Items = v1Response.items.filter((item) => !failedFromVersion(item, ['v2']));
|
||||
|
||||
if (filteredV1Items.length === v1Response.items.length) {
|
||||
return v1Response;
|
||||
}
|
||||
|
||||
const v2Response = await this.v2Client.listDashboardHistory(uid);
|
||||
const filteredV2Items = v2Response.items.filter((item) => !failedFromVersion(item, ['v0', 'v1']));
|
||||
|
||||
return {
|
||||
...v2Response,
|
||||
// Make sure we display only valid resources
|
||||
items: [...filteredV1Items, ...filteredV2Items].filter(isResource),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* List deleted dashboards handling mixed v1/v2 versions or pure v2 dashboards.
|
||||
*
|
||||
|
||||
@@ -12,6 +12,22 @@ import { SaveDashboardCommand } from '../components/SaveDashboard/types';
|
||||
|
||||
import { DashboardAPI, ListDeletedDashboardsOptions } from './types';
|
||||
|
||||
interface HistoryResult {
|
||||
continueToken?: string;
|
||||
versions: RevisionsModel[];
|
||||
}
|
||||
interface RevisionsModel {
|
||||
id: number;
|
||||
checked: boolean;
|
||||
uid: string;
|
||||
parentVersion: number;
|
||||
version: number;
|
||||
created: Date;
|
||||
createdBy: string;
|
||||
message: string;
|
||||
data: Dashboard;
|
||||
}
|
||||
|
||||
export class LegacyDashboardAPI implements DashboardAPI<DashboardDTO, Dashboard> {
|
||||
constructor() {}
|
||||
|
||||
@@ -53,6 +69,30 @@ export class LegacyDashboardAPI implements DashboardAPI<DashboardDTO, Dashboard>
|
||||
return result;
|
||||
}
|
||||
|
||||
async listDashboardHistory(uid: string): Promise<ResourceList<Dashboard, Dashboard, string>> {
|
||||
const result = await getBackendSrv().get<HistoryResult>(`/api/dashboards/uid/${uid}/versions`);
|
||||
return {
|
||||
apiVersion: 'v0alpha1',
|
||||
kind: 'DashboardList',
|
||||
metadata: { resourceVersion: '0' },
|
||||
items: result.versions.map((v) => ({
|
||||
apiVersion: 'v0alpha1',
|
||||
kind: 'Dashboard',
|
||||
metadata: {
|
||||
name: v.uid,
|
||||
resourceVersion: v.version.toString(),
|
||||
generation: v.version,
|
||||
creationTimestamp: v.created ? v.created.toISOString() : new Date().toISOString(),
|
||||
annotations: {
|
||||
'grafana.app/updatedBy': v.createdBy,
|
||||
'grafana.app/message': v.message,
|
||||
},
|
||||
},
|
||||
spec: v.data,
|
||||
})),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* No-op for legacy API
|
||||
*/
|
||||
|
||||
@@ -15,6 +15,8 @@ export interface DashboardAPI<G, T> {
|
||||
saveDashboard(options: SaveDashboardCommand<T>): Promise<SaveDashboardResponseDTO>;
|
||||
/** Delete a dashboard */
|
||||
deleteDashboard(uid: string, showSuccessAlert: boolean): Promise<DeleteDashboardResponse>;
|
||||
/** List all versions of a dashboard */
|
||||
listDashboardHistory(uid: string): Promise<ResourceList<T>>;
|
||||
/** List all deleted dashboards */
|
||||
listDeletedDashboards(options: ListDeletedDashboardsOptions): Promise<ResourceList<T>>;
|
||||
/** Restore a deleted dashboard by re-creating it */
|
||||
|
||||
@@ -217,6 +217,13 @@ export class K8sDashboardAPI implements DashboardAPI<DashboardDTO, Dashboard> {
|
||||
}
|
||||
}
|
||||
|
||||
async listDashboardHistory(uid: string) {
|
||||
return await this.client.list({
|
||||
labelSelector: 'grafana.app/get-history=true',
|
||||
fieldSelector: `metadata.name=${uid}`,
|
||||
});
|
||||
}
|
||||
|
||||
async listDeletedDashboards(options: ListDeletedDashboardsOptions) {
|
||||
return await this.client.list({ ...options, labelSelector: 'grafana.app/get-trash=true' });
|
||||
}
|
||||
|
||||
@@ -175,6 +175,13 @@ export class K8sDashboardV2API
|
||||
};
|
||||
}
|
||||
|
||||
async listDashboardHistory(uid: string) {
|
||||
return await this.client.list({
|
||||
labelSelector: 'grafana.app/get-history=true',
|
||||
fieldSelector: `metadata.name=${uid}`,
|
||||
});
|
||||
}
|
||||
|
||||
listDeletedDashboards(options: ListDeletedDashboardsOptions) {
|
||||
return this.client.list({ ...options, labelSelector: 'grafana.app/get-trash=true' });
|
||||
}
|
||||
|
||||
@@ -49,6 +49,7 @@ describe('validateUid', () => {
|
||||
saveDashboard: jest.fn(),
|
||||
listDeletedDashboards: jest.fn(),
|
||||
restoreDashboard: jest.fn(),
|
||||
listDashboardHistory: jest.fn(),
|
||||
},
|
||||
v2: {
|
||||
getDashboardDTO: jest.fn().mockResolvedValue(v2Dashboard),
|
||||
@@ -56,6 +57,7 @@ describe('validateUid', () => {
|
||||
saveDashboard: jest.fn(),
|
||||
listDeletedDashboards: jest.fn(),
|
||||
restoreDashboard: jest.fn(),
|
||||
listDashboardHistory: jest.fn(),
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
@@ -2,8 +2,20 @@ import { css } from '@emotion/css';
|
||||
import { useId, useState } from 'react';
|
||||
|
||||
import { createTheme, GrafanaTheme2, NewThemeOptions } from '@grafana/data';
|
||||
import { experimentalThemeDefinitions, NewThemeOptionsSchema } from '@grafana/data/internal';
|
||||
import { themeJsonSchema } from '@grafana/data/unstable';
|
||||
import { NewThemeOptionsSchema } from '@grafana/data/internal';
|
||||
import aubergine from '@grafana/data/themes/definitions/aubergine.json';
|
||||
import debug from '@grafana/data/themes/definitions/debug.json';
|
||||
import desertbloom from '@grafana/data/themes/definitions/desertbloom.json';
|
||||
import gildedgrove from '@grafana/data/themes/definitions/gildedgrove.json';
|
||||
import gloom from '@grafana/data/themes/definitions/gloom.json';
|
||||
import mars from '@grafana/data/themes/definitions/mars.json';
|
||||
import matrix from '@grafana/data/themes/definitions/matrix.json';
|
||||
import sapphiredusk from '@grafana/data/themes/definitions/sapphiredusk.json';
|
||||
import synthwave from '@grafana/data/themes/definitions/synthwave.json';
|
||||
import tron from '@grafana/data/themes/definitions/tron.json';
|
||||
import victorian from '@grafana/data/themes/definitions/victorian.json';
|
||||
import zen from '@grafana/data/themes/definitions/zen.json';
|
||||
import themeJsonSchema from '@grafana/data/themes/schema.generated.json';
|
||||
import { t } from '@grafana/i18n';
|
||||
import { useChromeHeaderHeight } from '@grafana/runtime';
|
||||
import { CodeEditor, Combobox, Field, Stack, useStyles2 } from '@grafana/ui';
|
||||
@@ -34,8 +46,23 @@ const themeMap: Record<string, NewThemeOptions> = {
|
||||
},
|
||||
};
|
||||
|
||||
const experimentalDefinitions: Record<string, unknown> = {
|
||||
aubergine,
|
||||
debug,
|
||||
desertbloom,
|
||||
gildedgrove,
|
||||
gloom,
|
||||
mars,
|
||||
matrix,
|
||||
sapphiredusk,
|
||||
synthwave,
|
||||
tron,
|
||||
victorian,
|
||||
zen,
|
||||
};
|
||||
|
||||
// Add additional themes
|
||||
for (const [name, json] of Object.entries(experimentalThemeDefinitions)) {
|
||||
for (const [name, json] of Object.entries(experimentalDefinitions)) {
|
||||
const result = NewThemeOptionsSchema.safeParse(json);
|
||||
if (!result.success) {
|
||||
console.error(`Invalid theme definition for theme ${name}: ${result.error.message}`);
|
||||
|
||||
@@ -11,6 +11,7 @@ failed_checks=()
|
||||
for file in "$ARTIFACTS_DIR"/*.tgz; do
|
||||
echo "🔍 Checking NPM package: $file"
|
||||
|
||||
# If you need to debug ATTW issues, pass "--format json" to get verbose output.
|
||||
if ! NODE_OPTIONS="-C @grafana-app/source" yarn attw "$file" --ignore-rules "false-cjs" --profile "node16"; then
|
||||
echo "attw check failed for $file"
|
||||
echo ""
|
||||
|
||||
Reference in New Issue
Block a user