mirror of https://github.com/grafana/grafana.git
				
				
				
			FEMT: Add `no-restricted-img-srcs` rule (#105006)
This commit is contained in:
		
							parent
							
								
									56cfeb8616
								
							
						
					
					
						commit
						8f17f607fa
					
				| 
						 | 
				
			
			@ -91,6 +91,7 @@ module.exports = [
 | 
			
		|||
      'no-duplicate-case': 'error',
 | 
			
		||||
      '@grafana/no-border-radius-literal': 'error',
 | 
			
		||||
      '@grafana/no-unreduced-motion': 'error',
 | 
			
		||||
      '@grafana/no-restricted-img-srcs': 'error',
 | 
			
		||||
      'react/prop-types': 'off',
 | 
			
		||||
      // need to ignore emotion's `css` prop, see https://github.com/jsx-eslint/eslint-plugin-react/blob/master/docs/rules/no-unknown-property.md#rule-options
 | 
			
		||||
      'react/no-unknown-property': ['error', { ignore: ['css'] }],
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -44,7 +44,7 @@ module.exports = {
 | 
			
		|||
    __webpack_public_path__: '', // empty string
 | 
			
		||||
  },
 | 
			
		||||
  moduleNameMapper: {
 | 
			
		||||
    '\\.svg': '<rootDir>/public/test/mocks/svg.ts',
 | 
			
		||||
    '\\.(svg|png|jpg)': '<rootDir>/public/test/mocks/images.ts',
 | 
			
		||||
    '\\.css': '<rootDir>/public/test/mocks/style.ts',
 | 
			
		||||
    'react-inlinesvg': '<rootDir>/public/test/mocks/react-inlinesvg.tsx',
 | 
			
		||||
    // resolve directly as monaco and kusto don't have main property in package.json which jest needs
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -4,6 +4,7 @@ const noUnreducedMotion = require('./rules/no-unreduced-motion.cjs');
 | 
			
		|||
const noUntranslatedStrings = require('./rules/no-untranslated-strings.cjs');
 | 
			
		||||
const noTranslationTopLevel = require('./rules/no-translation-top-level.cjs');
 | 
			
		||||
const themeTokenUsage = require('./rules/theme-token-usage.cjs');
 | 
			
		||||
const noRestrictedImgSrcs = require('./rules/no-restricted-img-srcs.cjs');
 | 
			
		||||
 | 
			
		||||
module.exports = {
 | 
			
		||||
  rules: {
 | 
			
		||||
| 
						 | 
				
			
			@ -13,5 +14,6 @@ module.exports = {
 | 
			
		|||
    'theme-token-usage': themeTokenUsage,
 | 
			
		||||
    'no-untranslated-strings': noUntranslatedStrings,
 | 
			
		||||
    'no-translation-top-level': noTranslationTopLevel,
 | 
			
		||||
    'no-restricted-img-srcs': noRestrictedImgSrcs,
 | 
			
		||||
  },
 | 
			
		||||
};
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -0,0 +1,96 @@
 | 
			
		|||
// @ts-check
 | 
			
		||||
const { AST_NODE_TYPES } = require('@typescript-eslint/utils');
 | 
			
		||||
const { upperFirst } = require('lodash');
 | 
			
		||||
 | 
			
		||||
/** @typedef {import('@typescript-eslint/utils/ts-eslint').RuleContext<'publicImg' | 'importImage' | 'useBuildFolder',  []>} RuleContextWithOptions */
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @param {string} str
 | 
			
		||||
 */
 | 
			
		||||
const camelCase = (str) => {
 | 
			
		||||
  return str
 | 
			
		||||
    .replace(/[-_]/g, ' ')
 | 
			
		||||
    .split(' ')
 | 
			
		||||
    .map((word, index) => (index === 0 ? word : upperFirst(word)))
 | 
			
		||||
    .join('');
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @param {string} value
 | 
			
		||||
 * @returns {string}
 | 
			
		||||
 */
 | 
			
		||||
const convertPathToImportName = (value) => {
 | 
			
		||||
  const fullFileName = value.split('/').pop() || '';
 | 
			
		||||
  const fileType = fullFileName.split('.').pop();
 | 
			
		||||
  const fileName = fullFileName.replace(`.${fileType}`, '');
 | 
			
		||||
  return camelCase(fileName) + upperFirst(fileType);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @param {import('@typescript-eslint/utils/ts-eslint').RuleFixer} fixer
 | 
			
		||||
 * @param {import('@typescript-eslint/utils').TSESTree.StringLiteral} node
 | 
			
		||||
 * @param {RuleContextWithOptions} context
 | 
			
		||||
 */
 | 
			
		||||
function getImageImportFixers(fixer, node, context) {
 | 
			
		||||
  const { value: importPath } = node;
 | 
			
		||||
  const pathWithoutPublic = importPath.replace('public/', '');
 | 
			
		||||
 | 
			
		||||
  /** e.g. public/img/checkbox.png -> checkboxPng */
 | 
			
		||||
  const imageImportName = convertPathToImportName(importPath);
 | 
			
		||||
 | 
			
		||||
  const body = context.sourceCode.ast.body;
 | 
			
		||||
 | 
			
		||||
  const existingImport = body.find(
 | 
			
		||||
    (node) => node.type === AST_NODE_TYPES.ImportDeclaration && node.source.value === pathWithoutPublic
 | 
			
		||||
  );
 | 
			
		||||
 | 
			
		||||
  const fixers = [];
 | 
			
		||||
 | 
			
		||||
  // If there's no existing import at all, add a fixer for this
 | 
			
		||||
  if (!existingImport) {
 | 
			
		||||
    const importStatementFixer = fixer.insertTextBefore(
 | 
			
		||||
      body[0],
 | 
			
		||||
      `import ${imageImportName} from '${pathWithoutPublic}';\n`
 | 
			
		||||
    );
 | 
			
		||||
    fixers.push(importStatementFixer);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  const isInAttribute = node.parent.type === AST_NODE_TYPES.JSXAttribute;
 | 
			
		||||
  const variableReplacement = isInAttribute ? `{${imageImportName}}` : imageImportName;
 | 
			
		||||
  const variableFixer = fixer.replaceText(node, variableReplacement);
 | 
			
		||||
  fixers.push(variableFixer);
 | 
			
		||||
 | 
			
		||||
  return fixers;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @param {import('@typescript-eslint/utils/ts-eslint').RuleFixer} fixer
 | 
			
		||||
 * @param {import('@typescript-eslint/utils').TSESTree.StringLiteral} node
 | 
			
		||||
 */
 | 
			
		||||
const replaceWithPublicBuild = (fixer, node) => {
 | 
			
		||||
  const { value } = node;
 | 
			
		||||
 | 
			
		||||
  const startingQuote = node.raw.startsWith('"') ? '"' : "'";
 | 
			
		||||
  return fixer.replaceText(
 | 
			
		||||
    node,
 | 
			
		||||
    `${startingQuote}${value.replace('public/img/', 'public/build/img/')}${startingQuote}`
 | 
			
		||||
  );
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @param {string} value
 | 
			
		||||
 */
 | 
			
		||||
const isInvalidImageLocation = (value) => {
 | 
			
		||||
  return (
 | 
			
		||||
    value.startsWith('public/img/') ||
 | 
			
		||||
    (!value.startsWith('public/build/') &&
 | 
			
		||||
      !value.startsWith('public/plugins/') &&
 | 
			
		||||
      /public.*(\.svg|\.png|\.jpg|\.jpeg|\.gif)$/.test(value))
 | 
			
		||||
  );
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
module.exports = {
 | 
			
		||||
  getImageImportFixers,
 | 
			
		||||
  replaceWithPublicBuild,
 | 
			
		||||
  isInvalidImageLocation,
 | 
			
		||||
};
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,78 @@
 | 
			
		|||
// @ts-check
 | 
			
		||||
/** @typedef {import('@typescript-eslint/utils').TSESTree.Literal} Literal */
 | 
			
		||||
/** @typedef {import('@typescript-eslint/utils').TSESTree.TemplateLiteral} TemplateLiteral */
 | 
			
		||||
const { getImageImportFixers, replaceWithPublicBuild, isInvalidImageLocation } = require('./import-utils.cjs');
 | 
			
		||||
 | 
			
		||||
const { ESLintUtils, AST_NODE_TYPES } = require('@typescript-eslint/utils');
 | 
			
		||||
 | 
			
		||||
const createRule = ESLintUtils.RuleCreator(
 | 
			
		||||
  (name) => `https://github.com/grafana/grafana/blob/main/packages/grafana-eslint-rules/README.md#${name}`
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
const imgSrcRule = createRule({
 | 
			
		||||
  create(context) {
 | 
			
		||||
    return {
 | 
			
		||||
      /**
 | 
			
		||||
       * @param {Literal|TemplateLiteral} node
 | 
			
		||||
       */
 | 
			
		||||
      'Literal, TemplateLiteral'(node) {
 | 
			
		||||
        if (node.type === AST_NODE_TYPES.TemplateLiteral) {
 | 
			
		||||
          if (node.quasis.some((quasi) => isInvalidImageLocation(quasi.value.raw))) {
 | 
			
		||||
            return context.report({
 | 
			
		||||
              node,
 | 
			
		||||
              messageId: 'publicImg',
 | 
			
		||||
            });
 | 
			
		||||
          }
 | 
			
		||||
          return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        const { value } = node;
 | 
			
		||||
 | 
			
		||||
        if (value && typeof value === 'string' && isInvalidImageLocation(value)) {
 | 
			
		||||
          const canUseBuildFolder = value.startsWith('public/img/');
 | 
			
		||||
          /**
 | 
			
		||||
           * @type {import('@typescript-eslint/utils/ts-eslint').SuggestionReportDescriptor<"publicImg" | "importImage" | "useBuildFolder">[]}
 | 
			
		||||
           */
 | 
			
		||||
          const suggestions = [
 | 
			
		||||
            {
 | 
			
		||||
              messageId: 'importImage',
 | 
			
		||||
              fix: (fixer) => getImageImportFixers(fixer, node, context),
 | 
			
		||||
            },
 | 
			
		||||
          ];
 | 
			
		||||
 | 
			
		||||
          if (canUseBuildFolder) {
 | 
			
		||||
            suggestions.push({
 | 
			
		||||
              messageId: 'useBuildFolder',
 | 
			
		||||
              fix: (fixer) => replaceWithPublicBuild(fixer, node),
 | 
			
		||||
            });
 | 
			
		||||
          }
 | 
			
		||||
 | 
			
		||||
          return context.report({
 | 
			
		||||
            node,
 | 
			
		||||
            messageId: 'publicImg',
 | 
			
		||||
            suggest: suggestions,
 | 
			
		||||
          });
 | 
			
		||||
        }
 | 
			
		||||
      },
 | 
			
		||||
    };
 | 
			
		||||
  },
 | 
			
		||||
  name: 'no-restricted-img-srcs',
 | 
			
		||||
  meta: {
 | 
			
		||||
    fixable: 'code',
 | 
			
		||||
    hasSuggestions: true,
 | 
			
		||||
    type: 'problem',
 | 
			
		||||
    docs: {
 | 
			
		||||
      description: 'Disallow references to images in the public folder',
 | 
			
		||||
    },
 | 
			
		||||
    messages: {
 | 
			
		||||
      publicImg:
 | 
			
		||||
        "Don't reference image sources from the public folder. Either use the build folder or import the image",
 | 
			
		||||
      importImage: 'Import image instead',
 | 
			
		||||
      useBuildFolder: 'Use public/build path instead',
 | 
			
		||||
    },
 | 
			
		||||
    schema: [],
 | 
			
		||||
  },
 | 
			
		||||
  defaultOptions: [],
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
module.exports = imgSrcRule;
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,143 @@
 | 
			
		|||
/* eslint-disable @grafana/no-restricted-img-srcs */
 | 
			
		||||
import { RuleTester } from 'eslint';
 | 
			
		||||
 | 
			
		||||
import noRestrictedImgSrcs from '../rules/no-restricted-img-srcs.cjs';
 | 
			
		||||
 | 
			
		||||
RuleTester.setDefaultConfig({
 | 
			
		||||
  languageOptions: {
 | 
			
		||||
    ecmaVersion: 2018,
 | 
			
		||||
    sourceType: 'module',
 | 
			
		||||
    parserOptions: {
 | 
			
		||||
      ecmaFeatures: {
 | 
			
		||||
        jsx: true,
 | 
			
		||||
      },
 | 
			
		||||
    },
 | 
			
		||||
  },
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const ruleTester = new RuleTester();
 | 
			
		||||
 | 
			
		||||
ruleTester.run('eslint no-restricted-img-srcs', noRestrictedImgSrcs, {
 | 
			
		||||
  valid: [
 | 
			
		||||
    {
 | 
			
		||||
      name: 'uses build folder',
 | 
			
		||||
      code: `const foo = 'public/build/img/checkbox.png';`,
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      name: 'uses import',
 | 
			
		||||
      code: `
 | 
			
		||||
import foo from 'img/checkbox.png';
 | 
			
		||||
const bar = foo;
 | 
			
		||||
const baz = <img src={foo} />;
 | 
			
		||||
`,
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      name: 'plugin folder',
 | 
			
		||||
      code: `const foo = 'public/plugins/foo/checkbox.png';`,
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      name: 'template literal',
 | 
			
		||||
      code: `const foo = \`something else\``,
 | 
			
		||||
    },
 | 
			
		||||
  ],
 | 
			
		||||
  invalid: [
 | 
			
		||||
    {
 | 
			
		||||
      name: 'references public folder',
 | 
			
		||||
      code: `
 | 
			
		||||
const foo = 'public/img/checkbox-128-icon.png';`,
 | 
			
		||||
      errors: [
 | 
			
		||||
        {
 | 
			
		||||
          messageId: 'publicImg',
 | 
			
		||||
          suggestions: [
 | 
			
		||||
            {
 | 
			
		||||
              messageId: 'importImage',
 | 
			
		||||
              output: `
 | 
			
		||||
import checkbox128IconPng from 'img/checkbox-128-icon.png';
 | 
			
		||||
const foo = checkbox128IconPng;`,
 | 
			
		||||
            },
 | 
			
		||||
            {
 | 
			
		||||
              messageId: 'useBuildFolder',
 | 
			
		||||
              output: `
 | 
			
		||||
const foo = 'public/build/img/checkbox-128-icon.png';`,
 | 
			
		||||
            },
 | 
			
		||||
          ],
 | 
			
		||||
        },
 | 
			
		||||
      ],
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      name: 'template literal',
 | 
			
		||||
      code: `
 | 
			
		||||
const isDark = true ? 'dark' : 'light';
 | 
			
		||||
const foo = \`public/img/checkbox-128-icon-\${isDark}.png\`;`,
 | 
			
		||||
      errors: [
 | 
			
		||||
        {
 | 
			
		||||
          messageId: 'publicImg',
 | 
			
		||||
        },
 | 
			
		||||
      ],
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      name: 'fixes jsx attribute',
 | 
			
		||||
      code: `<img src="public/img/checkbox.png" />`,
 | 
			
		||||
      errors: [
 | 
			
		||||
        {
 | 
			
		||||
          messageId: 'publicImg',
 | 
			
		||||
          suggestions: [
 | 
			
		||||
            {
 | 
			
		||||
              messageId: 'importImage',
 | 
			
		||||
              output: `import checkboxPng from 'img/checkbox.png';
 | 
			
		||||
<img src={checkboxPng} />`,
 | 
			
		||||
            },
 | 
			
		||||
            {
 | 
			
		||||
              messageId: 'useBuildFolder',
 | 
			
		||||
              output: `<img src="public/build/img/checkbox.png" />`,
 | 
			
		||||
            },
 | 
			
		||||
          ],
 | 
			
		||||
        },
 | 
			
		||||
      ],
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      name: 'fixes with existing import',
 | 
			
		||||
      code: `
 | 
			
		||||
import checkboxPng from 'img/checkbox.png';
 | 
			
		||||
const foo = checkboxPng;
 | 
			
		||||
const bar = 'public/img/checkbox.png';`,
 | 
			
		||||
      errors: [
 | 
			
		||||
        {
 | 
			
		||||
          messageId: 'publicImg',
 | 
			
		||||
          suggestions: [
 | 
			
		||||
            {
 | 
			
		||||
              messageId: 'importImage',
 | 
			
		||||
              output: `
 | 
			
		||||
import checkboxPng from 'img/checkbox.png';
 | 
			
		||||
const foo = checkboxPng;
 | 
			
		||||
const bar = checkboxPng;`,
 | 
			
		||||
            },
 | 
			
		||||
            {
 | 
			
		||||
              messageId: 'useBuildFolder',
 | 
			
		||||
              output: `
 | 
			
		||||
import checkboxPng from 'img/checkbox.png';
 | 
			
		||||
const foo = checkboxPng;
 | 
			
		||||
const bar = 'public/build/img/checkbox.png';`,
 | 
			
		||||
            },
 | 
			
		||||
          ],
 | 
			
		||||
        },
 | 
			
		||||
      ],
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      name: 'image elsewhere in public folder',
 | 
			
		||||
      code: `const foo = 'public/app/plugins/datasource/alertmanager/img/logo.svg';`,
 | 
			
		||||
      errors: [
 | 
			
		||||
        {
 | 
			
		||||
          messageId: 'publicImg',
 | 
			
		||||
          suggestions: [
 | 
			
		||||
            {
 | 
			
		||||
              messageId: 'importImage',
 | 
			
		||||
              output: `import logoSvg from 'app/plugins/datasource/alertmanager/img/logo.svg';
 | 
			
		||||
const foo = logoSvg;`,
 | 
			
		||||
            },
 | 
			
		||||
          ],
 | 
			
		||||
        },
 | 
			
		||||
      ],
 | 
			
		||||
    },
 | 
			
		||||
  ],
 | 
			
		||||
});
 | 
			
		||||
| 
						 | 
				
			
			@ -10,6 +10,7 @@ import {
 | 
			
		|||
  PluginSignatureStatus,
 | 
			
		||||
  PluginType,
 | 
			
		||||
} from '@grafana/data';
 | 
			
		||||
import iconGaugeSvg from 'app/plugins/panel/gauge/img/icon_gauge.svg';
 | 
			
		||||
 | 
			
		||||
import { reportInteraction } from '../utils';
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -249,8 +250,8 @@ function createPluginMetaInfo(info: Partial<PluginMetaInfo> = {}): PluginMetaInf
 | 
			
		|||
    description: 'Standard gauge visualization',
 | 
			
		||||
    links: [],
 | 
			
		||||
    logos: {
 | 
			
		||||
      large: 'public/app/plugins/panel/gauge/img/icon_gauge.svg',
 | 
			
		||||
      small: 'public/app/plugins/panel/gauge/img/icon_gauge.svg',
 | 
			
		||||
      large: iconGaugeSvg,
 | 
			
		||||
      small: iconGaugeSvg,
 | 
			
		||||
    },
 | 
			
		||||
    screenshots: [],
 | 
			
		||||
    updated: '',
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -9,6 +9,7 @@ const icons = rq('../../public/app/core/icons/cached.json');
 | 
			
		|||
const pkg = rq('./package.json');
 | 
			
		||||
 | 
			
		||||
const iconSrcPaths = icons.map((iconSubPath) => {
 | 
			
		||||
  // eslint-disable-next-line @grafana/no-restricted-img-srcs
 | 
			
		||||
  return `../../public/img/icons/${iconSubPath}.svg`;
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -30,7 +30,7 @@ describe('Icon utils', () => {
 | 
			
		|||
 | 
			
		||||
      it('should return icon root based on __grafana_public_path__', () => {
 | 
			
		||||
        const { getIconRoot } = require('./utils');
 | 
			
		||||
        expect(getIconRoot()).toEqual('somepath/public/img/icons/');
 | 
			
		||||
        expect(getIconRoot()).toEqual('somepath/public/build/img/icons/');
 | 
			
		||||
      });
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -42,7 +42,7 @@ describe('Icon utils', () => {
 | 
			
		|||
 | 
			
		||||
      it('should return default icon root', () => {
 | 
			
		||||
        const { getIconRoot } = require('./utils');
 | 
			
		||||
        expect(getIconRoot()).toEqual('public/img/icons/');
 | 
			
		||||
        expect(getIconRoot()).toEqual('public/build/img/icons/');
 | 
			
		||||
      });
 | 
			
		||||
    });
 | 
			
		||||
  });
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -53,9 +53,9 @@ export function getIconRoot(): string {
 | 
			
		|||
 | 
			
		||||
  const grafanaPublicPath = typeof window !== 'undefined' && window.__grafana_public_path__;
 | 
			
		||||
  if (grafanaPublicPath) {
 | 
			
		||||
    iconRoot = grafanaPublicPath + 'img/icons/';
 | 
			
		||||
    iconRoot = grafanaPublicPath + 'build/img/icons/';
 | 
			
		||||
  } else {
 | 
			
		||||
    iconRoot = 'public/img/icons/';
 | 
			
		||||
    iconRoot = 'public/build/img/icons/';
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return iconRoot;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -5,6 +5,7 @@ import { selectors } from '@grafana/e2e-selectors';
 | 
			
		|||
import { IconButton, Drawer, useStyles2, Text } from '@grafana/ui';
 | 
			
		||||
import { t } from 'app/core/internationalization';
 | 
			
		||||
import { DEFAULT_FEED_URL } from 'app/plugins/panel/news/constants';
 | 
			
		||||
import grotNewsSvg from 'img/grot-news.svg';
 | 
			
		||||
 | 
			
		||||
import { NewsWrapper } from './NewsWrapper';
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -28,7 +29,7 @@ export function NewsContainer({ onClose }: NewsContainerProps) {
 | 
			
		|||
            title={t('news.link-title', 'Go to Grafana labs blog')}
 | 
			
		||||
            className={styles.grot}
 | 
			
		||||
          >
 | 
			
		||||
            <img src="public/img/grot-news.svg" alt="Grot reading news" />
 | 
			
		||||
            <img src={grotNewsSvg} alt="Grot reading news" />
 | 
			
		||||
          </a>
 | 
			
		||||
          <div className={styles.actions}>
 | 
			
		||||
            <IconButton
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -6,6 +6,7 @@ import { GrafanaTheme2 } from '@grafana/data';
 | 
			
		|||
import { useStyles2 } from '@grafana/ui';
 | 
			
		||||
import { News } from 'app/plugins/panel/news/component/News';
 | 
			
		||||
import { useNewsFeed } from 'app/plugins/panel/news/useNewsFeed';
 | 
			
		||||
import grotNewsSvg from 'img/grot-news.svg';
 | 
			
		||||
 | 
			
		||||
import { t } from '../../../internationalization';
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -50,7 +51,7 @@ export function NewsWrapper({ feedUrl }: NewsWrapperProps) {
 | 
			
		|||
          rel="noreferrer"
 | 
			
		||||
          title={t('news.link-title', 'Go to Grafana labs blog')}
 | 
			
		||||
        >
 | 
			
		||||
          <img src="public/img/grot-news.svg" alt="Grot reading news" />
 | 
			
		||||
          <img src={grotNewsSvg} alt="Grot reading news" />
 | 
			
		||||
        </a>
 | 
			
		||||
      </div>
 | 
			
		||||
    </div>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -2,6 +2,7 @@ import { css, keyframes } from '@emotion/css';
 | 
			
		|||
 | 
			
		||||
import { GrafanaTheme2 } from '@grafana/data';
 | 
			
		||||
import { useStyles2 } from '@grafana/ui';
 | 
			
		||||
import grafanaIconSvg from 'img/grafana_icon.svg';
 | 
			
		||||
 | 
			
		||||
import { t } from '../../internationalization';
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -16,7 +17,7 @@ export function BouncingLoader() {
 | 
			
		|||
      aria-label={t('bouncing-loader.label', 'Loading')}
 | 
			
		||||
    >
 | 
			
		||||
      <div className={styles.bounce}>
 | 
			
		||||
        <img alt="" src="public/img/grafana_icon.svg" className={styles.logo} />
 | 
			
		||||
        <img alt="" src={grafanaIconSvg} className={styles.logo} />
 | 
			
		||||
      </div>
 | 
			
		||||
    </div>
 | 
			
		||||
  );
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -3,6 +3,9 @@ import { FC } from 'react';
 | 
			
		|||
 | 
			
		||||
import { colorManipulator } from '@grafana/data';
 | 
			
		||||
import { useTheme2 } from '@grafana/ui';
 | 
			
		||||
import g8LoginDarkSvg from 'img/g8_login_dark.svg';
 | 
			
		||||
import g8LoginLightSvg from 'img/g8_login_light.svg';
 | 
			
		||||
import grafanaIconSvg from 'img/grafana_icon.svg';
 | 
			
		||||
 | 
			
		||||
export interface BrandComponentProps {
 | 
			
		||||
  className?: string;
 | 
			
		||||
| 
						 | 
				
			
			@ -10,7 +13,7 @@ export interface BrandComponentProps {
 | 
			
		|||
}
 | 
			
		||||
 | 
			
		||||
export const LoginLogo: FC<BrandComponentProps & { logo?: string }> = ({ className, logo }) => {
 | 
			
		||||
  return <img className={className} src={`${logo ? logo : 'public/img/grafana_icon.svg'}`} alt="Grafana" />;
 | 
			
		||||
  return <img className={className} src={`${logo ? logo : grafanaIconSvg}`} alt="Grafana" />;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const LoginBackground: FC<BrandComponentProps> = ({ className, children }) => {
 | 
			
		||||
| 
						 | 
				
			
			@ -24,7 +27,7 @@ const LoginBackground: FC<BrandComponentProps> = ({ className, children }) => {
 | 
			
		|||
      right: 0,
 | 
			
		||||
      bottom: 0,
 | 
			
		||||
      top: 0,
 | 
			
		||||
      background: `url(public/img/g8_login_${theme.isDark ? 'dark' : 'light'}.svg)`,
 | 
			
		||||
      background: `url(${theme.isDark ? g8LoginDarkSvg : g8LoginLightSvg})`,
 | 
			
		||||
      backgroundPosition: 'top center',
 | 
			
		||||
      backgroundSize: 'auto',
 | 
			
		||||
      backgroundRepeat: 'no-repeat',
 | 
			
		||||
| 
						 | 
				
			
			@ -43,7 +46,7 @@ const LoginBackground: FC<BrandComponentProps> = ({ className, children }) => {
 | 
			
		|||
};
 | 
			
		||||
 | 
			
		||||
const MenuLogo: FC<BrandComponentProps> = ({ className }) => {
 | 
			
		||||
  return <img className={className} src="public/img/grafana_icon.svg" alt="Grafana" />;
 | 
			
		||||
  return <img className={className} src={grafanaIconSvg} alt="Grafana" />;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const LoginBoxBackground = () => {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -271,5 +271,5 @@ const getImgUrl = (urlOrId: string) => {
 | 
			
		|||
    return urlOrId;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return '/public/img/enterprise/highlights/' + urlOrId;
 | 
			
		||||
  return '/public/build/img/enterprise/highlights/' + urlOrId;
 | 
			
		||||
};
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -3,11 +3,14 @@ import * as React from 'react';
 | 
			
		|||
 | 
			
		||||
import { GrafanaTheme2 } from '@grafana/data';
 | 
			
		||||
import { useStyles2, useTheme2 } from '@grafana/ui';
 | 
			
		||||
import grafanaIconSvg from 'img/grafana_icon.svg';
 | 
			
		||||
import headerDarkSvg from 'img/licensing/header_dark.svg';
 | 
			
		||||
import headerLightSvg from 'img/licensing/header_light.svg';
 | 
			
		||||
 | 
			
		||||
const title = { fontWeight: 500, fontSize: '26px', lineHeight: '123%' };
 | 
			
		||||
 | 
			
		||||
const getStyles = (theme: GrafanaTheme2) => {
 | 
			
		||||
  const backgroundUrl = theme.isDark ? 'public/img/licensing/header_dark.svg' : 'public/img/licensing/header_light.svg';
 | 
			
		||||
  const backgroundUrl = theme.isDark ? headerDarkSvg : headerLightSvg;
 | 
			
		||||
  const footerBg = theme.isDark ? theme.v1.palette.dark9 : theme.v1.palette.gray6;
 | 
			
		||||
 | 
			
		||||
  return {
 | 
			
		||||
| 
						 | 
				
			
			@ -56,7 +59,7 @@ export function LicenseChrome({ header, editionNotice, subheader, children }: Pr
 | 
			
		|||
          }}
 | 
			
		||||
        >
 | 
			
		||||
          <img
 | 
			
		||||
            src="public/img/grafana_icon.svg"
 | 
			
		||||
            src={grafanaIconSvg}
 | 
			
		||||
            alt="Grafana"
 | 
			
		||||
            width="80px"
 | 
			
		||||
            style={{ position: 'absolute', left: '23px', top: '20px' }}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -6,6 +6,11 @@ import { GrafanaTheme2, NavModel } from '@grafana/data';
 | 
			
		|||
import { LinkButton, useStyles2 } from '@grafana/ui';
 | 
			
		||||
import { Page } from 'app/core/components/Page/Page';
 | 
			
		||||
import { Trans, t } from 'app/core/internationalization';
 | 
			
		||||
import checkmarkSvg from 'img/licensing/checkmark.svg';
 | 
			
		||||
import customerSupportSvg from 'img/licensing/customer_support.svg';
 | 
			
		||||
import handinhandSupportSvg from 'img/licensing/handinhand_support.svg';
 | 
			
		||||
import pluginEnterpriseSvg from 'img/licensing/plugin_enterprise.svg';
 | 
			
		||||
import slaSvg from 'img/licensing/sla.svg';
 | 
			
		||||
 | 
			
		||||
import { getNavModel } from '../../core/selectors/navModel';
 | 
			
		||||
import { StoreState } from '../../types';
 | 
			
		||||
| 
						 | 
				
			
			@ -112,15 +117,12 @@ const ServiceInfo = () => {
 | 
			
		|||
      <List>
 | 
			
		||||
        <Item
 | 
			
		||||
          title={t('admin.service-info.title-enterprise-plugins', 'Enterprise Plugins')}
 | 
			
		||||
          image="public/img/licensing/plugin_enterprise.svg"
 | 
			
		||||
        />
 | 
			
		||||
        <Item
 | 
			
		||||
          title={t('admin.service-info.title-critical-sla-hours', 'Critical SLA: 2 hours')}
 | 
			
		||||
          image="public/img/licensing/sla.svg"
 | 
			
		||||
          image={pluginEnterpriseSvg}
 | 
			
		||||
        />
 | 
			
		||||
        <Item title={t('admin.service-info.title-critical-sla-hours', 'Critical SLA: 2 hours')} image={slaSvg} />
 | 
			
		||||
        <Item
 | 
			
		||||
          title={t('admin.service-info.title-unlimited-expert-support', 'Unlimited Expert Support')}
 | 
			
		||||
          image="public/img/licensing/customer_support.svg"
 | 
			
		||||
          image={customerSupportSvg}
 | 
			
		||||
        >
 | 
			
		||||
          <Trans i18nKey="admin.service-info.year-round-support">24 × 7 × 365 support via</Trans>
 | 
			
		||||
          <List nested={true}>
 | 
			
		||||
| 
						 | 
				
			
			@ -134,7 +136,7 @@ const ServiceInfo = () => {
 | 
			
		|||
            'admin.service-info.title-handinhand-support-in-the-upgrade-process',
 | 
			
		||||
            'Hand-in-hand support in the upgrade process'
 | 
			
		||||
          )}
 | 
			
		||||
          image="public/img/licensing/handinhand_support.svg"
 | 
			
		||||
          image={handinhandSupportSvg}
 | 
			
		||||
        />
 | 
			
		||||
      </List>
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -241,7 +243,7 @@ interface ItemProps {
 | 
			
		|||
}
 | 
			
		||||
 | 
			
		||||
const Item = ({ children, title, image }: React.PropsWithChildren<ItemProps>) => {
 | 
			
		||||
  const imageUrl = image ? image : 'public/img/licensing/checkmark.svg';
 | 
			
		||||
  const imageUrl = image ? image : checkmarkSvg;
 | 
			
		||||
  const itemStyle = css({
 | 
			
		||||
    display: 'flex',
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -110,7 +110,7 @@ exports[`useContactPoints should return contact points with status 1`] = `
 | 
			
		|||
            "name": "Grafana IRM",
 | 
			
		||||
          },
 | 
			
		||||
          Symbol(receiver_plugin_metadata): {
 | 
			
		||||
            "icon": "public/img/alerting/oncall_logo.svg",
 | 
			
		||||
            "icon": "__DEFAULT_MOCK_IMAGE_CONTENT__",
 | 
			
		||||
            "title": "Grafana OnCall",
 | 
			
		||||
          },
 | 
			
		||||
        },
 | 
			
		||||
| 
						 | 
				
			
			@ -363,7 +363,7 @@ exports[`useContactPoints when having oncall plugin installed and no alert manag
 | 
			
		|||
          Symbol(receiver_plugin_metadata): {
 | 
			
		||||
            "description": "grafana-integration",
 | 
			
		||||
            "externalUrl": "/a/grafana-oncall-app/integrations/ABC123",
 | 
			
		||||
            "icon": "public/img/alerting/oncall_logo.svg",
 | 
			
		||||
            "icon": "__DEFAULT_MOCK_IMAGE_CONTENT__",
 | 
			
		||||
            "title": "Grafana OnCall",
 | 
			
		||||
            "warning": undefined,
 | 
			
		||||
          },
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,8 +1,11 @@
 | 
			
		|||
import irmLogoSvg from 'img/alerting/irm_logo.svg';
 | 
			
		||||
import oncallLogoSvg from 'img/alerting/oncall_logo.svg';
 | 
			
		||||
 | 
			
		||||
import { SupportedPlugin } from '../../../types/pluginBridges';
 | 
			
		||||
 | 
			
		||||
export const GRAFANA_APP_RECEIVERS_SOURCE_IMAGE: Record<SupportedPlugin, string> = {
 | 
			
		||||
  [SupportedPlugin.OnCall]: 'public/img/alerting/oncall_logo.svg',
 | 
			
		||||
  [SupportedPlugin.Irm]: 'public/img/alerting/irm_logo.svg',
 | 
			
		||||
  [SupportedPlugin.OnCall]: oncallLogoSvg,
 | 
			
		||||
  [SupportedPlugin.Irm]: irmLogoSvg,
 | 
			
		||||
  [SupportedPlugin.Incident]: '',
 | 
			
		||||
  [SupportedPlugin.MachineLearning]: '',
 | 
			
		||||
  [SupportedPlugin.Labels]: '',
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,3 +1,5 @@
 | 
			
		|||
import grafanaIconSvg from 'img/grafana_icon.svg';
 | 
			
		||||
 | 
			
		||||
import { Trans } from '../../../../../../core/internationalization';
 | 
			
		||||
import { RuleFormType } from '../../../types/rule-form';
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -16,7 +18,7 @@ const GrafanaManagedRuleType = ({ selected = false, disabled, onClick }: SharedP
 | 
			
		|||
          </Trans>
 | 
			
		||||
        </span>
 | 
			
		||||
      }
 | 
			
		||||
      image="public/img/grafana_icon.svg"
 | 
			
		||||
      image={grafanaIconSvg}
 | 
			
		||||
      selected={selected}
 | 
			
		||||
      disabled={disabled}
 | 
			
		||||
      value={RuleFormType.grafana}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,3 +1,5 @@
 | 
			
		|||
import mimirLogoSvg from 'img/alerting/mimir_logo.svg';
 | 
			
		||||
 | 
			
		||||
import { Trans } from '../../../../../../core/internationalization';
 | 
			
		||||
import { RuleFormType } from '../../../types/rule-form';
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -22,7 +24,7 @@ const MimirFlavoredType = ({ selected = false, disabled = false, onClick }: Prop
 | 
			
		|||
            </Trans>
 | 
			
		||||
          </span>
 | 
			
		||||
        }
 | 
			
		||||
        image="public/img/alerting/mimir_logo.svg"
 | 
			
		||||
        image={mimirLogoSvg}
 | 
			
		||||
        selected={selected}
 | 
			
		||||
        disabled={disabled}
 | 
			
		||||
        value={RuleFormType.cloudAlerting}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,3 +1,5 @@
 | 
			
		|||
import mimirLogoRecordingSvg from 'img/alerting/mimir_logo_recording.svg';
 | 
			
		||||
 | 
			
		||||
import { Trans } from '../../../../../../core/internationalization';
 | 
			
		||||
import { RuleFormType } from '../../../types/rule-form';
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -18,7 +20,7 @@ const RecordingRuleType = ({ selected = false, disabled = false, onClick }: Shar
 | 
			
		|||
            </Trans>
 | 
			
		||||
          </span>
 | 
			
		||||
        }
 | 
			
		||||
        image="public/img/alerting/mimir_logo_recording.svg"
 | 
			
		||||
        image={mimirLogoRecordingSvg}
 | 
			
		||||
        selected={selected}
 | 
			
		||||
        disabled={disabled}
 | 
			
		||||
        value={RuleFormType.cloudRecording}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -2,6 +2,7 @@ import { capitalize } from 'lodash';
 | 
			
		|||
 | 
			
		||||
import { Badge, Button, Card, Stack, Text, TextLink } from '@grafana/ui';
 | 
			
		||||
import { Trans, t } from 'app/core/internationalization';
 | 
			
		||||
import alertmanagerLogo from 'app/plugins/datasource/alertmanager/img/logo.svg';
 | 
			
		||||
 | 
			
		||||
import { ConnectionStatus } from '../../hooks/useExternalAmSelector';
 | 
			
		||||
import { ProvisioningBadge } from '../Provisioning';
 | 
			
		||||
| 
						 | 
				
			
			@ -28,7 +29,7 @@ export function AlertmanagerCard({
 | 
			
		|||
  name,
 | 
			
		||||
  href,
 | 
			
		||||
  url,
 | 
			
		||||
  logo = 'public/app/plugins/datasource/alertmanager/img/logo.svg',
 | 
			
		||||
  logo = alertmanagerLogo,
 | 
			
		||||
  provisioned = false,
 | 
			
		||||
  readOnly = provisioned,
 | 
			
		||||
  showStatus = true,
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,3 +1,5 @@
 | 
			
		|||
import grafanaIconSvg from 'img/grafana_icon.svg';
 | 
			
		||||
 | 
			
		||||
import { ConnectionStatus } from '../../hooks/useExternalAmSelector';
 | 
			
		||||
import { GRAFANA_RULES_SOURCE_NAME } from '../../utils/datasource';
 | 
			
		||||
import { isInternalAlertmanagerInterestedInAlerts } from '../../utils/settings';
 | 
			
		||||
| 
						 | 
				
			
			@ -24,7 +26,7 @@ export default function InternalAlertmanager({ onEditConfiguration }: Props) {
 | 
			
		|||
  return (
 | 
			
		||||
    <AlertmanagerCard
 | 
			
		||||
      name={BUILTIN_ALERTMANAGER_NAME}
 | 
			
		||||
      logo="public/img/grafana_icon.svg"
 | 
			
		||||
      logo={grafanaIconSvg}
 | 
			
		||||
      status={status}
 | 
			
		||||
      receiving={isReceiving}
 | 
			
		||||
      onEditConfiguration={handleEditConfiguration}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -5,11 +5,15 @@ import SVG from 'react-inlinesvg';
 | 
			
		|||
import { GrafanaTheme2 } from '@grafana/data';
 | 
			
		||||
import { Stack, Text, TextLink, useStyles2, useTheme2 } from '@grafana/ui';
 | 
			
		||||
import { Trans, t } from 'app/core/internationalization';
 | 
			
		||||
import atAGlanceDarkSvg from 'img/alerting/at_a_glance_dark.svg';
 | 
			
		||||
import atAGlanceLightSvg from 'img/alerting/at_a_glance_light.svg';
 | 
			
		||||
 | 
			
		||||
export default function GettingStarted() {
 | 
			
		||||
  const theme = useTheme2();
 | 
			
		||||
  const styles = useStyles2(getWelcomePageStyles);
 | 
			
		||||
 | 
			
		||||
  const atAGlanceImage = theme.name === 'dark' ? atAGlanceDarkSvg : atAGlanceLightSvg;
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <div className={styles.grid}>
 | 
			
		||||
      <ContentBox>
 | 
			
		||||
| 
						 | 
				
			
			@ -41,11 +45,7 @@ export default function GettingStarted() {
 | 
			
		|||
          </ul>
 | 
			
		||||
          <div className={styles.svgContainer}>
 | 
			
		||||
            <Stack justifyContent={'center'}>
 | 
			
		||||
              <SVG
 | 
			
		||||
                src={`public/img/alerting/at_a_glance_${theme.name.toLowerCase()}.svg`}
 | 
			
		||||
                width={undefined}
 | 
			
		||||
                height={undefined}
 | 
			
		||||
              />
 | 
			
		||||
              <SVG src={atAGlanceImage} width={undefined} height={undefined} />
 | 
			
		||||
            </Stack>
 | 
			
		||||
          </div>
 | 
			
		||||
        </Stack>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -4,6 +4,9 @@ import { PropsWithChildren } from 'react';
 | 
			
		|||
import { GrafanaTheme2 } from '@grafana/data';
 | 
			
		||||
import { Icon, Stack, TextLink, useStyles2 } from '@grafana/ui';
 | 
			
		||||
import { t } from 'app/core/internationalization';
 | 
			
		||||
import lokiIconSvg from 'app/plugins/datasource/loki/img/loki_icon.svg';
 | 
			
		||||
import mimirLogoSvg from 'app/plugins/datasource/prometheus/img/mimir_logo.svg';
 | 
			
		||||
import prometheusLogoSvg from 'app/plugins/datasource/prometheus/img/prometheus_logo.svg';
 | 
			
		||||
import { PromApplication, RulesSourceApplication } from 'app/types/unified-alerting-dto';
 | 
			
		||||
 | 
			
		||||
import { WithReturnButton } from '../../components/WithReturnButton';
 | 
			
		||||
| 
						 | 
				
			
			@ -54,20 +57,11 @@ interface NamespaceIconProps {
 | 
			
		|||
export const DataSourceIcon = ({ application, size = 16 }: NamespaceIconProps) => {
 | 
			
		||||
  switch (application) {
 | 
			
		||||
    case PromApplication.Prometheus:
 | 
			
		||||
      return (
 | 
			
		||||
        <img
 | 
			
		||||
          width={size}
 | 
			
		||||
          height={size}
 | 
			
		||||
          src="public/app/plugins/datasource/prometheus/img/prometheus_logo.svg"
 | 
			
		||||
          alt="Prometheus"
 | 
			
		||||
        />
 | 
			
		||||
      );
 | 
			
		||||
      return <img width={size} height={size} src={prometheusLogoSvg} alt="Prometheus" />;
 | 
			
		||||
    case PromApplication.Mimir:
 | 
			
		||||
      return (
 | 
			
		||||
        <img width={size} height={size} src="public/app/plugins/datasource/prometheus/img/mimir_logo.svg" alt="Mimir" />
 | 
			
		||||
      );
 | 
			
		||||
      return <img width={size} height={size} src={mimirLogoSvg} alt="Mimir" />;
 | 
			
		||||
    case 'Loki':
 | 
			
		||||
      return <img width={size} height={size} src="public/app/plugins/datasource/loki/img/loki_icon.svg" alt="Loki" />;
 | 
			
		||||
      return <img width={size} height={size} src={lokiIconSvg} alt="Loki" />;
 | 
			
		||||
    case 'grafana':
 | 
			
		||||
    default:
 | 
			
		||||
      return <Icon name="grafana" />;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -18,6 +18,7 @@ import {
 | 
			
		|||
  RulesSourceIdentifier,
 | 
			
		||||
  RulesSourceUid,
 | 
			
		||||
} from 'app/types/unified-alerting';
 | 
			
		||||
import grafanaIconSvg from 'img/grafana_icon.svg';
 | 
			
		||||
 | 
			
		||||
import { alertmanagerApi } from '../api/alertmanagerApi';
 | 
			
		||||
import { PERMISSIONS_CONTACT_POINTS } from '../components/contact-points/permissions';
 | 
			
		||||
| 
						 | 
				
			
			@ -112,7 +113,7 @@ export function isAlertmanagerDataSourceInterestedInAlerts(
 | 
			
		|||
 | 
			
		||||
const grafanaAlertManagerDataSource: AlertManagerDataSource = {
 | 
			
		||||
  name: GRAFANA_RULES_SOURCE_NAME,
 | 
			
		||||
  imgUrl: 'public/img/grafana_icon.svg',
 | 
			
		||||
  imgUrl: grafanaIconSvg,
 | 
			
		||||
  hasConfigurationAPI: true,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -10,6 +10,8 @@ import { Icon, TextLink, Themeable2, withTheme2 } from '@grafana/ui';
 | 
			
		|||
import appEvents from 'app/core/app_events';
 | 
			
		||||
import { Trans, t } from 'app/core/internationalization';
 | 
			
		||||
import { SHARED_DASHBOARD_QUERY } from 'app/plugins/datasource/dashboard/constants';
 | 
			
		||||
import grabDarkSvg from 'img/grab_dark.svg';
 | 
			
		||||
import grabLightSvg from 'img/grab_light.svg';
 | 
			
		||||
 | 
			
		||||
import { ShowConfirmModalEvent } from '../../../../types/events';
 | 
			
		||||
import { DashboardModel } from '../../state/DashboardModel';
 | 
			
		||||
| 
						 | 
				
			
			@ -175,6 +177,7 @@ export class UnthemedDashboardRow extends Component<DashboardRowProps> {
 | 
			
		|||
export const DashboardRow = withTheme2(UnthemedDashboardRow);
 | 
			
		||||
 | 
			
		||||
const getStyles = (theme: GrafanaTheme2) => {
 | 
			
		||||
  const dragHandle = theme.name === 'dark' ? grabDarkSvg : grabLightSvg;
 | 
			
		||||
  const actions = css({
 | 
			
		||||
    color: theme.colors.text.secondary,
 | 
			
		||||
    opacity: 0,
 | 
			
		||||
| 
						 | 
				
			
			@ -244,7 +247,7 @@ const getStyles = (theme: GrafanaTheme2) => {
 | 
			
		|||
      cursor: 'move',
 | 
			
		||||
      width: '16px',
 | 
			
		||||
      height: '100%',
 | 
			
		||||
      background: 'url("public/img/grab_dark.svg") no-repeat 50% 50%',
 | 
			
		||||
      background: `url("${dragHandle}") no-repeat 50% 50%`,
 | 
			
		||||
      backgroundSize: '8px',
 | 
			
		||||
      visibility: 'hidden',
 | 
			
		||||
      position: 'absolute',
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -3,10 +3,12 @@ import * as React from 'react';
 | 
			
		|||
 | 
			
		||||
import { GrafanaTheme2 } from '@grafana/data';
 | 
			
		||||
import { useStyles2, useTheme2 } from '@grafana/ui';
 | 
			
		||||
import grafanaTextLogoDarkSvg from 'img/grafana_text_logo_dark.svg';
 | 
			
		||||
import grafanaTextLogoLightSvg from 'img/grafana_text_logo_light.svg';
 | 
			
		||||
 | 
			
		||||
const FOOTER_URL = 'https://grafana.com/?src=grafananet&cnt=public-dashboards';
 | 
			
		||||
const GRAFANA_LOGO_LIGHT_URL = 'public/img/grafana_text_logo_light.svg';
 | 
			
		||||
const GRAFANA_LOGO_DARK_URL = 'public/img/grafana_text_logo_dark.svg';
 | 
			
		||||
const GRAFANA_LOGO_LIGHT_URL = grafanaTextLogoLightSvg;
 | 
			
		||||
const GRAFANA_LOGO_DARK_URL = grafanaTextLogoDarkSvg;
 | 
			
		||||
const GRAFANA_LOGO_DEFAULT_VALUE = 'grafana-logo';
 | 
			
		||||
 | 
			
		||||
export interface PublicDashboardCfg {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -289,7 +289,7 @@ function getTransformationGridStyles(theme: GrafanaTheme2) {
 | 
			
		|||
 | 
			
		||||
const getImagePath = (id: string, disabled: boolean) => {
 | 
			
		||||
  const folder = config.theme2.isDark ? 'dark' : 'light';
 | 
			
		||||
  return `public/img/transformations/${folder}/${id}.svg`;
 | 
			
		||||
  return `public/build/img/transformations/${folder}/${id}.svg`;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const TransformationDescriptionOverrides: { [key: string]: string } = {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,6 +1,7 @@
 | 
			
		|||
import { merge } from 'lodash';
 | 
			
		||||
 | 
			
		||||
import { DataSourceSettings, DataSourcePluginMeta, DataSourceJsonData } from '@grafana/data';
 | 
			
		||||
import amazonWebServicesPng from 'app/plugins/datasource/cloudwatch/img/amazon-web-services.png';
 | 
			
		||||
import { DataSourceSettingsState, PluginDashboard } from 'app/types';
 | 
			
		||||
 | 
			
		||||
export const getMockDashboard = (override?: Partial<PluginDashboard>) => ({
 | 
			
		||||
| 
						 | 
				
			
			@ -51,7 +52,7 @@ export const getMockDataSource = <T extends DataSourceJsonData>(
 | 
			
		|||
      orgId: 1,
 | 
			
		||||
      readOnly: false,
 | 
			
		||||
      type: 'cloudwatch',
 | 
			
		||||
      typeLogoUrl: 'public/app/plugins/datasource/cloudwatch/img/amazon-web-services.png',
 | 
			
		||||
      typeLogoUrl: amazonWebServicesPng,
 | 
			
		||||
      url: '',
 | 
			
		||||
      user: '',
 | 
			
		||||
      secureJsonFields: {},
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,6 +1,36 @@
 | 
			
		|||
import { DataSourcePluginMeta, PluginType } from '@grafana/data';
 | 
			
		||||
import { featureEnabled } from '@grafana/runtime';
 | 
			
		||||
import { DataSourcePluginCategory } from 'app/types';
 | 
			
		||||
import grafanaIconSvg from 'img/grafana_icon.svg';
 | 
			
		||||
import adobeAnalyticsSvg from 'img/plugins/adobe-analytics.svg';
 | 
			
		||||
import appdynamicsSvg from 'img/plugins/appdynamics.svg';
 | 
			
		||||
import atlassianStatuspageSvg from 'img/plugins/atlassian-statuspage.svg';
 | 
			
		||||
import auroraSvg from 'img/plugins/aurora.svg';
 | 
			
		||||
import azureCosmosdbSvg from 'img/plugins/azure-cosmosdb.svg';
 | 
			
		||||
import azureDevopsPng from 'img/plugins/azure-devops.png';
 | 
			
		||||
import catchpointSvg from 'img/plugins/catchpoint.svg';
 | 
			
		||||
import cloudflareJpg from 'img/plugins/cloudflare.jpg';
 | 
			
		||||
import cockroachdbJpg from 'img/plugins/cockroachdb.jpg';
 | 
			
		||||
import datadogPng from 'img/plugins/datadog.png';
 | 
			
		||||
import droneSvg from 'img/plugins/drone.svg';
 | 
			
		||||
import dynatracePng from 'img/plugins/dynatrace.png';
 | 
			
		||||
import gitlabSvg from 'img/plugins/gitlab.svg';
 | 
			
		||||
import honeycombPng from 'img/plugins/honeycomb.png';
 | 
			
		||||
import jiraLogoPng from 'img/plugins/jira_logo.png';
 | 
			
		||||
import mongodbSvg from 'img/plugins/mongodb.svg';
 | 
			
		||||
import netlifySvg from 'img/plugins/netlify.svg';
 | 
			
		||||
import newrelicSvg from 'img/plugins/newrelic.svg';
 | 
			
		||||
import oraclePng from 'img/plugins/oracle.png';
 | 
			
		||||
import pagerdutySvg from 'img/plugins/pagerduty.svg';
 | 
			
		||||
import salesforceSvg from 'img/plugins/salesforce.svg';
 | 
			
		||||
import sapHanaPng from 'img/plugins/sap_hana.png';
 | 
			
		||||
import servicenowSvg from 'img/plugins/servicenow.svg';
 | 
			
		||||
import signalfxLogoSvg from 'img/plugins/signalfx-logo.svg';
 | 
			
		||||
import snowflakeSvg from 'img/plugins/snowflake.svg';
 | 
			
		||||
import splunkLogo128Png from 'img/plugins/splunk_logo_128.png';
 | 
			
		||||
import sumoSvg from 'img/plugins/sumo.svg';
 | 
			
		||||
import wavefrontSvg from 'img/plugins/wavefront.svg';
 | 
			
		||||
import zendeskSvg from 'img/plugins/zendesk.svg';
 | 
			
		||||
 | 
			
		||||
export function buildCategories(plugins: DataSourcePluginMeta[]): DataSourcePluginCategory[] {
 | 
			
		||||
  const categories: DataSourcePluginCategory[] = [
 | 
			
		||||
| 
						 | 
				
			
			@ -99,175 +129,175 @@ function getEnterprisePhantomPlugins(): DataSourcePluginMeta[] {
 | 
			
		|||
      id: 'grafana-splunk-datasource',
 | 
			
		||||
      name: 'Splunk',
 | 
			
		||||
      description: 'Visualize and explore Splunk logs',
 | 
			
		||||
      imgUrl: 'public/img/plugins/splunk_logo_128.png',
 | 
			
		||||
      imgUrl: splunkLogo128Png,
 | 
			
		||||
    }),
 | 
			
		||||
    getPhantomPlugin({
 | 
			
		||||
      id: 'grafana-oracle-datasource',
 | 
			
		||||
      name: 'Oracle',
 | 
			
		||||
      description: 'Visualize and explore Oracle SQL',
 | 
			
		||||
      imgUrl: 'public/img/plugins/oracle.png',
 | 
			
		||||
      imgUrl: oraclePng,
 | 
			
		||||
    }),
 | 
			
		||||
    getPhantomPlugin({
 | 
			
		||||
      id: 'grafana-dynatrace-datasource',
 | 
			
		||||
      name: 'Dynatrace',
 | 
			
		||||
      description: 'Visualize and explore Dynatrace data',
 | 
			
		||||
      imgUrl: 'public/img/plugins/dynatrace.png',
 | 
			
		||||
      imgUrl: dynatracePng,
 | 
			
		||||
    }),
 | 
			
		||||
    getPhantomPlugin({
 | 
			
		||||
      id: 'grafana-servicenow-datasource',
 | 
			
		||||
      description: 'ServiceNow integration and data source',
 | 
			
		||||
      name: 'ServiceNow',
 | 
			
		||||
      imgUrl: 'public/img/plugins/servicenow.svg',
 | 
			
		||||
      imgUrl: servicenowSvg,
 | 
			
		||||
    }),
 | 
			
		||||
    getPhantomPlugin({
 | 
			
		||||
      id: 'grafana-datadog-datasource',
 | 
			
		||||
      description: 'DataDog integration and data source',
 | 
			
		||||
      name: 'DataDog',
 | 
			
		||||
      imgUrl: 'public/img/plugins/datadog.png',
 | 
			
		||||
      imgUrl: datadogPng,
 | 
			
		||||
    }),
 | 
			
		||||
    getPhantomPlugin({
 | 
			
		||||
      id: 'grafana-newrelic-datasource',
 | 
			
		||||
      description: 'New Relic integration and data source',
 | 
			
		||||
      name: 'New Relic',
 | 
			
		||||
      imgUrl: 'public/img/plugins/newrelic.svg',
 | 
			
		||||
      imgUrl: newrelicSvg,
 | 
			
		||||
    }),
 | 
			
		||||
    getPhantomPlugin({
 | 
			
		||||
      id: 'grafana-mongodb-datasource',
 | 
			
		||||
      description: 'MongoDB integration and data source',
 | 
			
		||||
      name: 'MongoDB',
 | 
			
		||||
      imgUrl: 'public/img/plugins/mongodb.svg',
 | 
			
		||||
      imgUrl: mongodbSvg,
 | 
			
		||||
    }),
 | 
			
		||||
    getPhantomPlugin({
 | 
			
		||||
      id: 'grafana-snowflake-datasource',
 | 
			
		||||
      description: 'Snowflake integration and data source',
 | 
			
		||||
      name: 'Snowflake',
 | 
			
		||||
      imgUrl: 'public/img/plugins/snowflake.svg',
 | 
			
		||||
      imgUrl: snowflakeSvg,
 | 
			
		||||
    }),
 | 
			
		||||
    getPhantomPlugin({
 | 
			
		||||
      id: 'grafana-wavefront-datasource',
 | 
			
		||||
      description: 'Wavefront integration and data source',
 | 
			
		||||
      name: 'Wavefront',
 | 
			
		||||
      imgUrl: 'public/img/plugins/wavefront.svg',
 | 
			
		||||
      imgUrl: wavefrontSvg,
 | 
			
		||||
    }),
 | 
			
		||||
    getPhantomPlugin({
 | 
			
		||||
      id: 'dlopes7-appdynamics-datasource',
 | 
			
		||||
      description: 'AppDynamics integration and data source',
 | 
			
		||||
      name: 'AppDynamics',
 | 
			
		||||
      imgUrl: 'public/img/plugins/appdynamics.svg',
 | 
			
		||||
      imgUrl: appdynamicsSvg,
 | 
			
		||||
    }),
 | 
			
		||||
    getPhantomPlugin({
 | 
			
		||||
      id: 'grafana-saphana-datasource',
 | 
			
		||||
      description: 'SAP HANA® integration and data source',
 | 
			
		||||
      name: 'SAP HANA®',
 | 
			
		||||
      imgUrl: 'public/img/plugins/sap_hana.png',
 | 
			
		||||
      imgUrl: sapHanaPng,
 | 
			
		||||
    }),
 | 
			
		||||
    getPhantomPlugin({
 | 
			
		||||
      id: 'grafana-honeycomb-datasource',
 | 
			
		||||
      description: 'Honeycomb integration and datasource',
 | 
			
		||||
      name: 'Honeycomb',
 | 
			
		||||
      imgUrl: 'public/img/plugins/honeycomb.png',
 | 
			
		||||
      imgUrl: honeycombPng,
 | 
			
		||||
    }),
 | 
			
		||||
    getPhantomPlugin({
 | 
			
		||||
      id: 'grafana-salesforce-datasource',
 | 
			
		||||
      description: 'Salesforce integration and datasource',
 | 
			
		||||
      name: 'Salesforce',
 | 
			
		||||
      imgUrl: 'public/img/plugins/salesforce.svg',
 | 
			
		||||
      imgUrl: salesforceSvg,
 | 
			
		||||
    }),
 | 
			
		||||
    getPhantomPlugin({
 | 
			
		||||
      id: 'grafana-jira-datasource',
 | 
			
		||||
      description: 'Jira integration and datasource',
 | 
			
		||||
      name: 'Jira',
 | 
			
		||||
      imgUrl: 'public/img/plugins/jira_logo.png',
 | 
			
		||||
      imgUrl: jiraLogoPng,
 | 
			
		||||
    }),
 | 
			
		||||
    getPhantomPlugin({
 | 
			
		||||
      id: 'grafana-gitlab-datasource',
 | 
			
		||||
      description: 'GitLab integration and datasource',
 | 
			
		||||
      name: 'GitLab',
 | 
			
		||||
      imgUrl: 'public/img/plugins/gitlab.svg',
 | 
			
		||||
      imgUrl: gitlabSvg,
 | 
			
		||||
    }),
 | 
			
		||||
    getPhantomPlugin({
 | 
			
		||||
      id: 'grafana-splunk-monitoring-datasource',
 | 
			
		||||
      description: 'SignalFx integration and datasource',
 | 
			
		||||
      name: 'Splunk Infrastructure Monitoring',
 | 
			
		||||
      imgUrl: 'public/img/plugins/signalfx-logo.svg',
 | 
			
		||||
      imgUrl: signalfxLogoSvg,
 | 
			
		||||
    }),
 | 
			
		||||
    getPhantomPlugin({
 | 
			
		||||
      id: 'grafana-azuredevops-datasource',
 | 
			
		||||
      description: 'Azure Devops datasource',
 | 
			
		||||
      name: 'Azure Devops',
 | 
			
		||||
      imgUrl: 'public/img/plugins/azure-devops.png',
 | 
			
		||||
      imgUrl: azureDevopsPng,
 | 
			
		||||
    }),
 | 
			
		||||
    getPhantomPlugin({
 | 
			
		||||
      id: 'grafana-sumologic-datasource',
 | 
			
		||||
      description: 'SumoLogic integration and datasource',
 | 
			
		||||
      name: 'SumoLogic',
 | 
			
		||||
      imgUrl: 'public/img/plugins/sumo.svg',
 | 
			
		||||
      imgUrl: sumoSvg,
 | 
			
		||||
    }),
 | 
			
		||||
    getPhantomPlugin({
 | 
			
		||||
      id: 'grafana-pagerduty-datasource',
 | 
			
		||||
      description: 'PagerDuty datasource',
 | 
			
		||||
      name: 'PagerDuty',
 | 
			
		||||
      imgUrl: 'public/img/plugins/pagerduty.svg',
 | 
			
		||||
      imgUrl: pagerdutySvg,
 | 
			
		||||
    }),
 | 
			
		||||
    getPhantomPlugin({
 | 
			
		||||
      id: 'grafana-catchpoint-datasource',
 | 
			
		||||
      description: 'Catchpoint datasource',
 | 
			
		||||
      name: 'Catchpoint',
 | 
			
		||||
      imgUrl: 'public/img/plugins/catchpoint.svg',
 | 
			
		||||
      imgUrl: catchpointSvg,
 | 
			
		||||
    }),
 | 
			
		||||
    getPhantomPlugin({
 | 
			
		||||
      id: 'grafana-azurecosmosdb-datasource',
 | 
			
		||||
      description: 'Azure CosmosDB datasource',
 | 
			
		||||
      name: 'Azure CosmosDB',
 | 
			
		||||
      imgUrl: 'public/img/plugins/azure-cosmosdb.svg',
 | 
			
		||||
      imgUrl: azureCosmosdbSvg,
 | 
			
		||||
    }),
 | 
			
		||||
    getPhantomPlugin({
 | 
			
		||||
      id: 'grafana-adobeanalytics-datasource',
 | 
			
		||||
      description: 'Adobe Analytics datasource',
 | 
			
		||||
      name: 'Adobe Analytics',
 | 
			
		||||
      imgUrl: 'public/img/plugins/adobe-analytics.svg',
 | 
			
		||||
      imgUrl: adobeAnalyticsSvg,
 | 
			
		||||
    }),
 | 
			
		||||
    getPhantomPlugin({
 | 
			
		||||
      id: 'grafana-cloudflare-datasource',
 | 
			
		||||
      description: 'Cloudflare datasource',
 | 
			
		||||
      name: 'Cloudflare',
 | 
			
		||||
      imgUrl: 'public/img/plugins/cloudflare.jpg',
 | 
			
		||||
      imgUrl: cloudflareJpg,
 | 
			
		||||
    }),
 | 
			
		||||
    getPhantomPlugin({
 | 
			
		||||
      id: 'grafana-cockroachdb-datasource',
 | 
			
		||||
      description: 'CockroachDB datasource',
 | 
			
		||||
      name: 'CockroachDB',
 | 
			
		||||
      imgUrl: 'public/img/plugins/cockroachdb.jpg',
 | 
			
		||||
      imgUrl: cockroachdbJpg,
 | 
			
		||||
    }),
 | 
			
		||||
    getPhantomPlugin({
 | 
			
		||||
      id: 'grafana-netlify-datasource',
 | 
			
		||||
      description: 'Netlify datasource',
 | 
			
		||||
      name: 'Netlify',
 | 
			
		||||
      imgUrl: 'public/img/plugins/netlify.svg',
 | 
			
		||||
      imgUrl: netlifySvg,
 | 
			
		||||
    }),
 | 
			
		||||
    getPhantomPlugin({
 | 
			
		||||
      id: 'grafana-drone-datasource',
 | 
			
		||||
      description: 'Drone datasource',
 | 
			
		||||
      name: 'Drone',
 | 
			
		||||
      imgUrl: 'public/img/plugins/drone.svg',
 | 
			
		||||
      imgUrl: droneSvg,
 | 
			
		||||
    }),
 | 
			
		||||
    getPhantomPlugin({
 | 
			
		||||
      id: 'grafana-zendesk-datasource',
 | 
			
		||||
      description: 'Zendesk datasource',
 | 
			
		||||
      name: 'Zendesk',
 | 
			
		||||
      imgUrl: 'public/img/plugins/zendesk.svg',
 | 
			
		||||
      imgUrl: zendeskSvg,
 | 
			
		||||
    }),
 | 
			
		||||
    getPhantomPlugin({
 | 
			
		||||
      id: 'grafana-atlassianstatuspage-datasource',
 | 
			
		||||
      description: 'Atlassian Statuspage datasource',
 | 
			
		||||
      name: 'Atlassian Statuspage',
 | 
			
		||||
      imgUrl: 'public/img/plugins/atlassian-statuspage.svg',
 | 
			
		||||
      imgUrl: atlassianStatuspageSvg,
 | 
			
		||||
    }),
 | 
			
		||||
    getPhantomPlugin({
 | 
			
		||||
      id: 'grafana-aurora-datasource',
 | 
			
		||||
      description: 'Aurora data source',
 | 
			
		||||
      name: 'Aurora',
 | 
			
		||||
      imgUrl: 'public/img/plugins/aurora.svg',
 | 
			
		||||
      imgUrl: auroraSvg,
 | 
			
		||||
    }),
 | 
			
		||||
  ];
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -281,7 +311,7 @@ function getGrafanaCloudPhantomPlugin(): DataSourcePluginMeta {
 | 
			
		|||
    baseUrl: '',
 | 
			
		||||
    info: {
 | 
			
		||||
      description: 'Hosted Graphite, Prometheus, and Loki',
 | 
			
		||||
      logos: { small: 'public/img/grafana_icon.svg', large: 'asd' },
 | 
			
		||||
      logos: { small: grafanaIconSvg, large: grafanaIconSvg },
 | 
			
		||||
      author: { name: 'Grafana Labs' },
 | 
			
		||||
      links: [
 | 
			
		||||
        {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -5,6 +5,7 @@ import config from 'app/core/config';
 | 
			
		|||
import { contextSrv } from 'app/core/core';
 | 
			
		||||
import { highlightTrial } from 'app/features/admin/utils';
 | 
			
		||||
import { AccessControlAction } from 'app/types';
 | 
			
		||||
import icnDatasourceSvg from 'img/icn-datasource.svg';
 | 
			
		||||
 | 
			
		||||
import { GenericDataSourcePlugin } from '../types';
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -169,7 +170,7 @@ export function getDataSourceLoadingNav(pageName: string): NavModel {
 | 
			
		|||
      readOnly: false,
 | 
			
		||||
      type: loadingDSType,
 | 
			
		||||
      typeName: loadingDSType,
 | 
			
		||||
      typeLogoUrl: 'public/img/icn-datasource.svg',
 | 
			
		||||
      typeLogoUrl: icnDatasourceSvg,
 | 
			
		||||
      url: '',
 | 
			
		||||
      user: '',
 | 
			
		||||
      secureJsonFields: {},
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -133,7 +133,7 @@ export const ResourcePicker = (props: Props) => {
 | 
			
		|||
 | 
			
		||||
// strip the SVG off icons in the icons folder
 | 
			
		||||
function getDisplayName(src?: string, name?: string): string | undefined {
 | 
			
		||||
  if (src?.startsWith('public/img/icons')) {
 | 
			
		||||
  if (src?.startsWith('public/build/img/icons')) {
 | 
			
		||||
    const idx = name?.lastIndexOf('.svg') ?? 0;
 | 
			
		||||
    if (idx > 0) {
 | 
			
		||||
      return name!.substring(0, idx);
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -19,6 +19,7 @@ import { setQueries } from 'app/features/explore/state/query';
 | 
			
		|||
import { dispatch } from 'app/store/store';
 | 
			
		||||
import { ShowConfirmModalEvent } from 'app/types/events';
 | 
			
		||||
import { RichHistoryQuery } from 'app/types/explore';
 | 
			
		||||
import icnDatasourceSvg from 'img/icn-datasource.svg';
 | 
			
		||||
 | 
			
		||||
import ExploreRunQueryButton from '../ExploreRunQueryButton';
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -419,7 +420,7 @@ function DatasourceInfo({ dsApi, size }: { dsApi?: DataSourceApi; size: 'sm' | '
 | 
			
		|||
  return (
 | 
			
		||||
    <div className={styles}>
 | 
			
		||||
      <img
 | 
			
		||||
        src={dsApi?.meta.info.logos.small || 'public/img/icn-datasource.svg'}
 | 
			
		||||
        src={dsApi?.meta.info.logos.small || icnDatasourceSvg}
 | 
			
		||||
        alt={dsApi?.type || t('explore.rich-history-card.datasource-not-exist', 'Data source does not exist anymore')}
 | 
			
		||||
        aria-label={t('explore.rich-history-card.datasource-icon-label', 'Data source icon')}
 | 
			
		||||
      />
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -10,6 +10,7 @@ import {
 | 
			
		|||
} from '@grafana/data';
 | 
			
		||||
import { DataSourceWithBackend, getDataSourceSrv, getTemplateSrv } from '@grafana/runtime';
 | 
			
		||||
import { ExpressionDatasourceRef } from '@grafana/runtime/internal';
 | 
			
		||||
import icnDatasourceSvg from 'img/icn-datasource.svg';
 | 
			
		||||
 | 
			
		||||
import { ExpressionQueryEditor } from './ExpressionQueryEditor';
 | 
			
		||||
import { ExpressionDatasourceUID, ExpressionQuery, ExpressionQueryType } from './types';
 | 
			
		||||
| 
						 | 
				
			
			@ -77,8 +78,8 @@ export const instanceSettings: DataSourceInstanceSettings = {
 | 
			
		|||
        name: 'Grafana Labs',
 | 
			
		||||
      },
 | 
			
		||||
      logos: {
 | 
			
		||||
        small: 'public/img/icn-datasource.svg',
 | 
			
		||||
        large: 'public/img/icn-datasource.svg',
 | 
			
		||||
        small: icnDatasourceSvg,
 | 
			
		||||
        large: icnDatasourceSvg,
 | 
			
		||||
      },
 | 
			
		||||
      description: 'Adds expression support to Grafana',
 | 
			
		||||
      screenshots: [],
 | 
			
		||||
| 
						 | 
				
			
			@ -96,8 +97,8 @@ dataSource.meta = {
 | 
			
		|||
  id: ExpressionDatasourceRef.type,
 | 
			
		||||
  info: {
 | 
			
		||||
    logos: {
 | 
			
		||||
      small: 'public/img/icn-datasource.svg',
 | 
			
		||||
      large: 'public/img/icn-datasource.svg',
 | 
			
		||||
      small: icnDatasourceSvg,
 | 
			
		||||
      large: icnDatasourceSvg,
 | 
			
		||||
    },
 | 
			
		||||
  },
 | 
			
		||||
} as DataSourcePluginMeta;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -5,6 +5,7 @@ import { PureComponent, ReactNode } from 'react';
 | 
			
		|||
import { PanelProps, PanelPlugin, PluginType, PanelPluginMeta } from '@grafana/data';
 | 
			
		||||
import { Alert } from '@grafana/ui';
 | 
			
		||||
import { AppNotificationSeverity } from 'app/types';
 | 
			
		||||
import grafanaIconSvg from 'img/grafana_icon.svg';
 | 
			
		||||
 | 
			
		||||
import { t, Trans } from '../../../core/internationalization';
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -85,7 +86,7 @@ export function getPanelPluginNotFound(id: string, silent?: boolean): PanelPlugi
 | 
			
		|||
      links: [],
 | 
			
		||||
      logos: {
 | 
			
		||||
        large: '',
 | 
			
		||||
        small: 'public/img/grafana_icon.svg',
 | 
			
		||||
        small: grafanaIconSvg,
 | 
			
		||||
      },
 | 
			
		||||
      screenshots: [],
 | 
			
		||||
      updated: '',
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -222,8 +222,8 @@ export function mapToCatalogPlugin(local?: LocalPlugin, remote?: RemotePlugin, e
 | 
			
		|||
  const keywords = remote?.keywords || local?.info.keywords || [];
 | 
			
		||||
 | 
			
		||||
  let logos = {
 | 
			
		||||
    small: `/public/img/icn-${type}.svg`,
 | 
			
		||||
    large: `/public/img/icn-${type}.svg`,
 | 
			
		||||
    small: `/public/build/img/icn-${type}.svg`,
 | 
			
		||||
    large: `/public/build/img/icn-${type}.svg`,
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  if (remote) {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -5,6 +5,7 @@ import { GrafanaTheme2 } from '@grafana/data';
 | 
			
		|||
import { Alert, Stack, useStyles2 } from '@grafana/ui';
 | 
			
		||||
import { useGetFrontendSettingsQuery, Repository } from 'app/api/clients/provisioning';
 | 
			
		||||
import { t, Trans } from 'app/core/internationalization';
 | 
			
		||||
import provisioningSvg from 'img/provisioning/provisioning.svg';
 | 
			
		||||
 | 
			
		||||
import { EnhancedFeatures } from './EnhancedFeatures';
 | 
			
		||||
import { FeaturesList } from './FeaturesList';
 | 
			
		||||
| 
						 | 
				
			
			@ -154,11 +155,7 @@ export default function GettingStarted({ items }: Props) {
 | 
			
		|||
            }}
 | 
			
		||||
          />
 | 
			
		||||
          <div className={styles.imageContainer}>
 | 
			
		||||
            <img
 | 
			
		||||
              src={'public/img/provisioning/provisioning.svg'}
 | 
			
		||||
              className={styles.image}
 | 
			
		||||
              alt={'Grafana provisioning'}
 | 
			
		||||
            />
 | 
			
		||||
            <img src={provisioningSvg} className={styles.image} alt={'Grafana provisioning'} />
 | 
			
		||||
          </div>
 | 
			
		||||
        </Stack>
 | 
			
		||||
        {(!hasPublicAccess || !hasImageRenderer) && hasItems && (
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -5,9 +5,10 @@ import config from 'app/core/config';
 | 
			
		|||
import { contextSrv } from 'app/core/services/context_srv';
 | 
			
		||||
import { highlightTrial } from 'app/features/admin/utils';
 | 
			
		||||
import { AccessControlAction, Team, TeamPermissionLevel } from 'app/types';
 | 
			
		||||
import userProfilePng from 'img/user_profile.png';
 | 
			
		||||
 | 
			
		||||
const loadingTeam = {
 | 
			
		||||
  avatarUrl: 'public/img/user_profile.png',
 | 
			
		||||
  avatarUrl: userProfilePng,
 | 
			
		||||
  id: 1,
 | 
			
		||||
  uid: '',
 | 
			
		||||
  name: 'Loading',
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -6,6 +6,8 @@ import { GrafanaTheme2, VariableOption } from '@grafana/data';
 | 
			
		|||
import { selectors } from '@grafana/e2e-selectors';
 | 
			
		||||
import { Tooltip, Themeable2, withTheme2, clearButtonStyles, stylesFactory } from '@grafana/ui';
 | 
			
		||||
import { Trans, t } from 'app/core/internationalization';
 | 
			
		||||
import checkboxPng from 'img/checkbox.png';
 | 
			
		||||
import checkboxWhitePng from 'img/checkbox_white.png';
 | 
			
		||||
 | 
			
		||||
import { ALL_VARIABLE_VALUE } from '../../constants';
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -139,7 +141,7 @@ class VariableOptions extends PureComponent<Props> {
 | 
			
		|||
}
 | 
			
		||||
 | 
			
		||||
const getStyles = stylesFactory((theme: GrafanaTheme2) => {
 | 
			
		||||
  const checkboxImageUrl = theme.isDark ? 'public/img/checkbox.png' : 'public/img/checkbox_white.png';
 | 
			
		||||
  const checkboxImageUrl = theme.isDark ? checkboxPng : checkboxWhitePng;
 | 
			
		||||
 | 
			
		||||
  return {
 | 
			
		||||
    hideVariableOptionIcon: css({
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -73,7 +73,7 @@ exports[`VariableQueryEditor renders correctly 1`] = `
 | 
			
		|||
                aria-hidden="true"
 | 
			
		||||
                class="css-1d3xu67-Icon"
 | 
			
		||||
                height="16"
 | 
			
		||||
                id="public/img/icons/unicons/angle-down.svg"
 | 
			
		||||
                id="public/build/img/icons/unicons/angle-down.svg"
 | 
			
		||||
                loader="[object Object]"
 | 
			
		||||
                title=""
 | 
			
		||||
                width="16"
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,5 +1,6 @@
 | 
			
		|||
import { VisualizationSuggestionsBuilder } from '@grafana/data';
 | 
			
		||||
import { TableFieldOptions } from '@grafana/schema';
 | 
			
		||||
import icnTablePanelSvg from 'app/plugins/panel/table/img/icn-table-panel.svg';
 | 
			
		||||
import { SuggestionName } from 'app/types/suggestions';
 | 
			
		||||
 | 
			
		||||
import { Options } from './panelcfg.gen';
 | 
			
		||||
| 
						 | 
				
			
			@ -27,7 +28,7 @@ export class TableSuggestionsSupplier {
 | 
			
		|||
    if (builder.dataSummary.fieldCount === 0) {
 | 
			
		||||
      list.append({
 | 
			
		||||
        cardOptions: {
 | 
			
		||||
          imgSrc: 'public/app/plugins/panel/table/img/icn-table-panel.svg',
 | 
			
		||||
          imgSrc: icnTablePanelSvg,
 | 
			
		||||
        },
 | 
			
		||||
      });
 | 
			
		||||
    } else {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,5 +1,6 @@
 | 
			
		|||
import { VisualizationSuggestionsBuilder } from '@grafana/data';
 | 
			
		||||
import { TableFieldOptions } from '@grafana/schema';
 | 
			
		||||
import icnTablePanelSvg from 'app/plugins/panel/table/img/icn-table-panel.svg';
 | 
			
		||||
import { SuggestionName } from 'app/types/suggestions';
 | 
			
		||||
 | 
			
		||||
import { Options } from './panelcfg.gen';
 | 
			
		||||
| 
						 | 
				
			
			@ -27,7 +28,7 @@ export class TableSuggestionsSupplier {
 | 
			
		|||
    if (builder.dataSummary.fieldCount === 0) {
 | 
			
		||||
      list.append({
 | 
			
		||||
        cardOptions: {
 | 
			
		||||
          imgSrc: 'public/app/plugins/panel/table/img/icn-table-panel.svg',
 | 
			
		||||
          imgSrc: icnTablePanelSvg,
 | 
			
		||||
        },
 | 
			
		||||
      });
 | 
			
		||||
    } else {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -2,3 +2,6 @@ declare module '*.svg' {
 | 
			
		|||
  const content: string;
 | 
			
		||||
  export default content;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
declare module '*.png';
 | 
			
		||||
declare module '*.jpg';
 | 
			
		||||
| 
						 | 
				
			
			@ -7,6 +7,7 @@ import { createTheme, monacoLanguageRegistry, SelectableValue } from '@grafana/d
 | 
			
		|||
import { Stack, Select, UserIcon, UserView, Button } from '@grafana/ui';
 | 
			
		||||
import { setMonacoEnv } from 'app/core/monacoEnv';
 | 
			
		||||
import { ThemeProvider } from 'app/core/utils/ConfigProvider';
 | 
			
		||||
import grafanaIconSvg from 'img/grafana_icon.svg';
 | 
			
		||||
 | 
			
		||||
import { Trans } from '../app/core/internationalization';
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -85,7 +86,7 @@ export const Page = () => {
 | 
			
		|||
        <NamespaceContext.Provider value={namespace.value}>
 | 
			
		||||
          <div style={{ backgroundColor: '#000', padding: '10px' }}>
 | 
			
		||||
            <Stack justifyContent={'space-between'}>
 | 
			
		||||
              <img height="40" src="public/img/grafana_icon.svg" alt="Grafana" />
 | 
			
		||||
              <img height="40" src={grafanaIconSvg} alt="Grafana" />
 | 
			
		||||
              <Select
 | 
			
		||||
                options={urls.value}
 | 
			
		||||
                isClearable={false /* TODO -- when we allow a landing page, this can be true */}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -0,0 +1 @@
 | 
			
		|||
export default '__DEFAULT_MOCK_IMAGE_CONTENT__';
 | 
			
		||||
| 
						 | 
				
			
			@ -1 +0,0 @@
 | 
			
		|||
export const svg = 'svg';
 | 
			
		||||
| 
						 | 
				
			
			@ -1,3 +1,4 @@
 | 
			
		|||
const CopyWebpackPlugin = require('copy-webpack-plugin');
 | 
			
		||||
const path = require('path');
 | 
			
		||||
const webpack = require('webpack');
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -68,6 +69,14 @@ module.exports = {
 | 
			
		|||
    new webpack.ProvidePlugin({
 | 
			
		||||
      Buffer: ['buffer', 'Buffer'],
 | 
			
		||||
    }),
 | 
			
		||||
    new CopyWebpackPlugin({
 | 
			
		||||
      patterns: [
 | 
			
		||||
        {
 | 
			
		||||
          from: 'public/img',
 | 
			
		||||
          to: 'img',
 | 
			
		||||
        },
 | 
			
		||||
      ],
 | 
			
		||||
    }),
 | 
			
		||||
  ],
 | 
			
		||||
  module: {
 | 
			
		||||
    rules: [
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in New Issue