FEMT: Add `no-restricted-img-srcs` rule (#105006)

pull/105241/head
Tom Ratcliffe 1 week ago committed by GitHub
parent 56cfeb8616
commit 8f17f607fa
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 1
      eslint.config.js
  2. 2
      jest.config.js
  3. 2
      packages/grafana-eslint-rules/index.cjs
  4. 96
      packages/grafana-eslint-rules/rules/import-utils.cjs
  5. 78
      packages/grafana-eslint-rules/rules/no-restricted-img-srcs.cjs
  6. 143
      packages/grafana-eslint-rules/tests/no-restricted-img-srcs.test.js
  7. 5
      packages/grafana-runtime/src/analytics/plugins/usePluginInteractionReporter.test.tsx
  8. 1
      packages/grafana-ui/rollup.config.ts
  9. 4
      packages/grafana-ui/src/components/Icon/utils.test.ts
  10. 4
      packages/grafana-ui/src/components/Icon/utils.ts
  11. 3
      public/app/core/components/AppChrome/News/NewsDrawer.tsx
  12. 3
      public/app/core/components/AppChrome/News/NewsWrapper.tsx
  13. 3
      public/app/core/components/BouncingLoader/BouncingLoader.tsx
  14. 9
      public/app/core/components/Branding/Branding.tsx
  15. 2
      public/app/core/components/Upgrade/UpgradeBox.tsx
  16. 7
      public/app/features/admin/LicenseChrome.tsx
  17. 18
      public/app/features/admin/UpgradePage.tsx
  18. 4
      public/app/features/alerting/unified/components/contact-points/__snapshots__/useContactPoints.test.tsx.snap
  19. 7
      public/app/features/alerting/unified/components/receivers/grafanaAppReceivers/types.ts
  20. 4
      public/app/features/alerting/unified/components/rule-editor/rule-types/GrafanaManagedAlert.tsx
  21. 4
      public/app/features/alerting/unified/components/rule-editor/rule-types/MimirOrLokiAlert.tsx
  22. 4
      public/app/features/alerting/unified/components/rule-editor/rule-types/MimirOrLokiRecordingRule.tsx
  23. 3
      public/app/features/alerting/unified/components/settings/AlertmanagerCard.tsx
  24. 4
      public/app/features/alerting/unified/components/settings/InternalAlertmanager.tsx
  25. 10
      public/app/features/alerting/unified/home/GettingStarted.tsx
  26. 18
      public/app/features/alerting/unified/rule-list/components/Namespace.tsx
  27. 3
      public/app/features/alerting/unified/utils/datasource.ts
  28. 5
      public/app/features/dashboard/components/DashboardRow/DashboardRow.tsx
  29. 6
      public/app/features/dashboard/components/PublicDashboard/usePublicDashboardConfig.tsx
  30. 2
      public/app/features/dashboard/components/TransformationsEditor/TransformationPickerNg.tsx
  31. 3
      public/app/features/datasources/__mocks__/dataSourcesMocks.ts
  32. 90
      public/app/features/datasources/state/buildCategories.ts
  33. 3
      public/app/features/datasources/state/navModel.ts
  34. 2
      public/app/features/dimensions/editors/ResourcePicker.tsx
  35. 3
      public/app/features/explore/RichHistory/RichHistoryCard.tsx
  36. 9
      public/app/features/expressions/ExpressionDatasource.ts
  37. 3
      public/app/features/panel/components/PanelPluginError.tsx
  38. 4
      public/app/features/plugins/admin/helpers.ts
  39. 7
      public/app/features/provisioning/GettingStarted/GettingStarted.tsx
  40. 3
      public/app/features/teams/state/navModel.ts
  41. 4
      public/app/features/variables/pickers/shared/VariableOptions.tsx
  42. 2
      public/app/plugins/datasource/cloud-monitoring/components/__snapshots__/VariableQueryEditor.test.tsx.snap
  43. 3
      public/app/plugins/panel/table/suggestions.ts
  44. 3
      public/app/plugins/panel/table/table-new/suggestions.ts
  45. 3
      public/app/types/images.d.ts
  46. 3
      public/swagger/SwaggerPage.tsx
  47. 1
      public/test/mocks/images.ts
  48. 1
      public/test/mocks/svg.ts
  49. 9
      scripts/webpack/webpack.common.js

@ -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…
Cancel
Save