mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Plugins: show signing status on datasources and plugins (#23542)
* show signing status * show signing status * Progress on signed badge style * Progress on signing status look and updated card background * Updates * Transforms card tweak Co-authored-by: Torkel Ödegaard <torkel@grafana.com>
This commit is contained in:
parent
68ba60adaf
commit
3d89f04562
@ -14,6 +14,14 @@ export enum PluginType {
|
||||
renderer = 'renderer',
|
||||
}
|
||||
|
||||
export enum PluginSignatureStatus {
|
||||
internal = 'internal', // core plugin, no signature
|
||||
valid = 'valid', // signed and accurate MANIFEST
|
||||
invalid = 'invalid', // invalid signature
|
||||
modified = 'modified', // valid signature, but content mismatch
|
||||
unsigned = 'unsigned', // no MANIFEST file
|
||||
}
|
||||
|
||||
export interface PluginMeta<T extends KeyValue = {}> {
|
||||
id: string;
|
||||
name: string;
|
||||
@ -38,6 +46,7 @@ export interface PluginMeta<T extends KeyValue = {}> {
|
||||
enterprise?: boolean;
|
||||
latestVersion?: string;
|
||||
pinned?: boolean;
|
||||
signature?: PluginSignatureStatus;
|
||||
}
|
||||
|
||||
interface PluginDependencyInfo {
|
||||
|
@ -2,6 +2,7 @@
|
||||
|
||||
import { GrafanaTheme } from '@grafana/data';
|
||||
import { renderGeneratedFileBanner } from '../utils/generatedFileBanner';
|
||||
import { styleMixins } from '.';
|
||||
|
||||
export const darkThemeVarsTemplate = (theme: GrafanaTheme) =>
|
||||
`${renderGeneratedFileBanner('grafana-ui/src/themes/dark.ts', 'grafana-ui/src/themes/_variables.dark.scss.tmpl.ts')}
|
||||
@ -140,9 +141,9 @@ $code-tag-bg: $dark-1;
|
||||
$code-tag-border: $dark-9;
|
||||
|
||||
// cards
|
||||
$card-background: linear-gradient(135deg, $dark-4, $dark-3);
|
||||
$card-background-hover: linear-gradient(135deg, $dark-5, $dark-6);
|
||||
$card-shadow: -1px -1px 0 0 hsla(0, 0%, 100%, 0.1), 1px 1px 0 0 rgba(0, 0, 0, 0.3);
|
||||
$card-background: ${theme.colors.bg2};
|
||||
$card-background-hover: ${styleMixins.hoverColor(theme.colors.bg2, theme)};
|
||||
$card-shadow: none;
|
||||
|
||||
// Lists
|
||||
$list-item-bg: $card-background;
|
||||
|
@ -2,6 +2,7 @@
|
||||
|
||||
import { GrafanaTheme } from '@grafana/data';
|
||||
import { renderGeneratedFileBanner } from '../utils/generatedFileBanner';
|
||||
import { styleMixins } from '.';
|
||||
|
||||
export const lightThemeVarsTemplate = (theme: GrafanaTheme) =>
|
||||
`${renderGeneratedFileBanner('grafana-ui/src/themes/light.ts', 'grafana-ui/src/themes/_variable.light.scss.tmpl.ts')}
|
||||
@ -133,9 +134,9 @@ $code-tag-bg: $gray-6;
|
||||
$code-tag-border: $gray-4;
|
||||
|
||||
// cards
|
||||
$card-background: linear-gradient(135deg, $gray-6, $gray-7);
|
||||
$card-background-hover: linear-gradient(135deg, $gray-6, $gray-5);
|
||||
$card-shadow: -1px -1px 0 0 hsla(0, 0%, 100%, 0.1), 1px 1px 0 0 rgba(0, 0, 0, 0.1);
|
||||
$card-background: ${theme.colors.bg2};
|
||||
$card-background-hover: ${styleMixins.hoverColor(theme.colors.bg2, theme)};
|
||||
$card-shadow: none;
|
||||
|
||||
// Lists
|
||||
$list-item-bg: $gray-7;
|
||||
|
@ -129,7 +129,7 @@ const darkTheme: GrafanaTheme = {
|
||||
linkExternal: basicColors.blue85,
|
||||
},
|
||||
shadows: {
|
||||
listItem: '-1px -1px 0 0 hsla(0, 0%, 100%, 0.1), 1px 1px 0 0 rgba(0, 0, 0, 0.3)',
|
||||
listItem: 'none',
|
||||
},
|
||||
};
|
||||
|
||||
|
@ -130,7 +130,7 @@ const lightTheme: GrafanaTheme = {
|
||||
textHeading: basicColors.gray25,
|
||||
},
|
||||
shadows: {
|
||||
listItem: '-1px -1px 0 0 hsla(0, 0%, 100%, 0.1), 1px 1px 0 0 rgba(0, 0, 0, 0.1)',
|
||||
listItem: 'none',
|
||||
},
|
||||
};
|
||||
|
||||
|
@ -1,12 +1,9 @@
|
||||
import React, { PureComponent } from 'react';
|
||||
import LayoutSelector, { LayoutMode } from '../LayoutSelector/LayoutSelector';
|
||||
import { FilterInput } from '../FilterInput/FilterInput';
|
||||
import { LinkButton } from '@grafana/ui';
|
||||
|
||||
export interface Props {
|
||||
searchQuery: string;
|
||||
layoutMode?: LayoutMode;
|
||||
onSetLayoutMode?: (mode: LayoutMode) => {};
|
||||
setSearchQuery: (value: string) => {};
|
||||
linkButton: { href: string; title: string };
|
||||
target?: string;
|
||||
@ -14,7 +11,7 @@ export interface Props {
|
||||
|
||||
export default class OrgActionBar extends PureComponent<Props> {
|
||||
render() {
|
||||
const { searchQuery, layoutMode, onSetLayoutMode, linkButton, setSearchQuery, target } = this.props;
|
||||
const { searchQuery, linkButton, setSearchQuery, target } = this.props;
|
||||
const linkProps = { href: linkButton.href };
|
||||
|
||||
if (target) {
|
||||
@ -31,7 +28,6 @@ export default class OrgActionBar extends PureComponent<Props> {
|
||||
onChange={setSearchQuery}
|
||||
placeholder={'Search by name or type'}
|
||||
/>
|
||||
<LayoutSelector mode={layoutMode} onLayoutModeChanged={(mode: LayoutMode) => onSetLayoutMode(mode)} />
|
||||
</div>
|
||||
<div className="page-action-bar__spacer" />
|
||||
<LinkButton {...linkProps}>{linkButton.title}</LinkButton>
|
||||
|
@ -14,9 +14,6 @@ exports[`Render should render component 1`] = `
|
||||
placeholder="Search by name or type"
|
||||
value=""
|
||||
/>
|
||||
<LayoutSelector
|
||||
onLayoutModeChanged={[Function]}
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
className="page-action-bar__spacer"
|
||||
|
@ -1,14 +1,5 @@
|
||||
import React from 'react';
|
||||
import {
|
||||
Container,
|
||||
CustomScrollbar,
|
||||
InfoBox,
|
||||
ValuePicker,
|
||||
Button,
|
||||
useTheme,
|
||||
VerticalGroup,
|
||||
stylesFactory,
|
||||
} from '@grafana/ui';
|
||||
import { Container, CustomScrollbar, ValuePicker, Button, useTheme, VerticalGroup, stylesFactory } from '@grafana/ui';
|
||||
import {
|
||||
DataFrame,
|
||||
DataTransformerConfig,
|
||||
@ -120,33 +111,39 @@ export class TransformationsEditor extends React.PureComponent<Props> {
|
||||
);
|
||||
};
|
||||
|
||||
renderNoAddedTransformsState() {
|
||||
return (
|
||||
<>
|
||||
<p className="muted">
|
||||
Transformations allow you to combine, re-order, hide and rename specific parts the the data set before being
|
||||
visualized. <br />
|
||||
Choose one of the transformations below to start with:
|
||||
</p>
|
||||
|
||||
<VerticalGroup>
|
||||
{standardTransformersRegistry.list().map(t => {
|
||||
return (
|
||||
<TransformationCard
|
||||
title={t.name}
|
||||
description={t.description}
|
||||
actions={<Button>Select</Button>}
|
||||
onClick={() => {
|
||||
this.onTransformationAdd({ value: t.id });
|
||||
}}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</VerticalGroup>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
const hasTransformationsConfigured = this.props.transformations.length > 0;
|
||||
return (
|
||||
<CustomScrollbar autoHeightMin="100%">
|
||||
<Container padding="md">
|
||||
{!hasTransformationsConfigured && (
|
||||
<InfoBox>
|
||||
<p>
|
||||
Transformations allow you to combine, re-order, hide and rename specific parts the the data set before
|
||||
being visualized. Choose one of the transformations below to start with:
|
||||
</p>
|
||||
<VerticalGroup>
|
||||
{standardTransformersRegistry.list().map(t => {
|
||||
return (
|
||||
<TransformationCard
|
||||
title={t.name}
|
||||
description={t.description}
|
||||
actions={<Button>Select</Button>}
|
||||
onClick={() => {
|
||||
this.onTransformationAdd({ value: t.id });
|
||||
}}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</VerticalGroup>
|
||||
</InfoBox>
|
||||
)}
|
||||
{!hasTransformationsConfigured && this.renderNoAddedTransformsState()}
|
||||
{hasTransformationsConfigured && this.renderTransformationEditors()}
|
||||
{hasTransformationsConfigured && this.renderTransformationSelector()}
|
||||
</Container>
|
||||
@ -166,8 +163,13 @@ const getTransformationCardStyles = stylesFactory((theme: GrafanaTheme) => {
|
||||
card: css`
|
||||
background: ${theme.colors.bg2};
|
||||
width: 100%;
|
||||
border: none;
|
||||
padding: ${theme.spacing.sm};
|
||||
|
||||
&:hover {
|
||||
background: ${theme.colors.bg3};
|
||||
box-shadow: none;
|
||||
border: none;
|
||||
}
|
||||
`,
|
||||
};
|
||||
|
@ -64,7 +64,6 @@ export class DataSourcesListPage extends PureComponent<Props> {
|
||||
layoutMode,
|
||||
searchQuery,
|
||||
setDataSourcesSearchQuery,
|
||||
setDataSourcesLayoutMode,
|
||||
hasFetched,
|
||||
} = this.props;
|
||||
|
||||
@ -81,9 +80,7 @@ export class DataSourcesListPage extends PureComponent<Props> {
|
||||
{hasFetched &&
|
||||
dataSourcesCount > 0 && [
|
||||
<OrgActionBar
|
||||
layoutMode={layoutMode}
|
||||
searchQuery={searchQuery}
|
||||
onSetLayoutMode={mode => setDataSourcesLayoutMode(mode)}
|
||||
setSearchQuery={query => setDataSourcesSearchQuery(query)}
|
||||
linkButton={linkButton}
|
||||
key="action-bar"
|
||||
|
@ -11,6 +11,7 @@ import { addDataSource, loadDataSourcePlugins } from './state/actions';
|
||||
import { getDataSourcePlugins } from './state/selectors';
|
||||
import { FilterInput } from 'app/core/components/FilterInput/FilterInput';
|
||||
import { setDataSourceTypeSearchQuery } from './state/reducers';
|
||||
import { PluginSignatureBadge } from '../plugins/PluginSignatureBadge';
|
||||
import { Card } from 'app/core/components/Card/Card';
|
||||
|
||||
export interface Props {
|
||||
@ -146,7 +147,34 @@ const DataSourceTypeCard: FC<DataSourceTypeCardProps> = props => {
|
||||
}
|
||||
className={isPhantom && 'add-data-source-item--phantom'}
|
||||
onClick={onClick}
|
||||
/>
|
||||
aria-label={e2e.pages.AddDataSource.selectors.dataSourcePlugins(plugin.name)}
|
||||
>
|
||||
<img className="add-data-source-item-logo" src={plugin.info.logos.small} />
|
||||
<div className="add-data-source-item-text-wrapper">
|
||||
<span className="add-data-source-item-text">{plugin.name}</span>
|
||||
{plugin.info.description && <span className="add-data-source-item-desc">{plugin.info.description}</span>}
|
||||
{!isPhantom && (
|
||||
<div>
|
||||
<PluginSignatureBadge status={plugin.signature} />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="add-data-source-item-actions">
|
||||
{learnMoreLink && (
|
||||
<LinkButton
|
||||
variant="secondary"
|
||||
href={`${learnMoreLink.url}?utm_source=grafana_add_ds`}
|
||||
target="_blank"
|
||||
rel="noopener"
|
||||
onClick={onLearnMoreClick}
|
||||
icon="external-link-alt"
|
||||
>
|
||||
{learnMoreLink.name}
|
||||
</LinkButton>
|
||||
)}
|
||||
{!isPhantom && <Button>Select</Button>}
|
||||
</div>
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
|
||||
|
@ -18,14 +18,12 @@ exports[`Render should render action bar and datasources 1`] = `
|
||||
>
|
||||
<OrgActionBar
|
||||
key="action-bar"
|
||||
layoutMode="grid"
|
||||
linkButton={
|
||||
Object {
|
||||
"href": "datasources/new",
|
||||
"title": "Add data source",
|
||||
}
|
||||
}
|
||||
onSetLayoutMode={[Function]}
|
||||
searchQuery=""
|
||||
setSearchQuery={[Function]}
|
||||
/>
|
||||
|
@ -1,25 +1,16 @@
|
||||
import React, { FC } from 'react';
|
||||
import classNames from 'classnames';
|
||||
import PluginListItem from './PluginListItem';
|
||||
import { PluginMeta } from '@grafana/data';
|
||||
import { LayoutMode, LayoutModes } from '../../core/components/LayoutSelector/LayoutSelector';
|
||||
|
||||
interface Props {
|
||||
plugins: PluginMeta[];
|
||||
layoutMode: LayoutMode;
|
||||
}
|
||||
|
||||
const PluginList: FC<Props> = props => {
|
||||
const { plugins, layoutMode } = props;
|
||||
|
||||
const listStyle = classNames({
|
||||
'card-section': true,
|
||||
'card-list-layout-grid': layoutMode === LayoutModes.Grid,
|
||||
'card-list-layout-list': layoutMode === LayoutModes.List,
|
||||
});
|
||||
const { plugins } = props;
|
||||
|
||||
return (
|
||||
<section className={listStyle}>
|
||||
<section className="card-section card-list-layout-list">
|
||||
<ol className="card-list">
|
||||
{plugins.map((plugin, index) => {
|
||||
return <PluginListItem plugin={plugin} key={`${plugin.name}-${index}`} />;
|
||||
|
@ -1,5 +1,6 @@
|
||||
import React, { FC } from 'react';
|
||||
import { PluginMeta } from '@grafana/data';
|
||||
import { PluginSignatureBadge } from './PluginSignatureBadge';
|
||||
|
||||
interface Props {
|
||||
plugin: PluginMeta;
|
||||
@ -13,6 +14,7 @@ const PluginListItem: FC<Props> = props => {
|
||||
<a className="card-item" href={`plugins/${plugin.id}/`}>
|
||||
<div className="card-item-header">
|
||||
<div className="card-item-type">{plugin.type}</div>
|
||||
<PluginSignatureBadge status={plugin.signature} />
|
||||
{plugin.hasUpdate && (
|
||||
<div className="card-item-notice">
|
||||
<span bs-tooltip="plugin.latestVersion">Update available!</span>
|
||||
|
@ -1,10 +1,9 @@
|
||||
import React from 'react';
|
||||
import { shallow } from 'enzyme';
|
||||
import { PluginListPage, Props } from './PluginListPage';
|
||||
import { LayoutModes } from '../../core/components/LayoutSelector/LayoutSelector';
|
||||
import { NavModel, PluginMeta } from '@grafana/data';
|
||||
import { mockToolkitActionCreator } from 'test/core/redux/mocks';
|
||||
import { setPluginsLayoutMode, setPluginsSearchQuery } from './state/reducers';
|
||||
import { setPluginsSearchQuery } from './state/reducers';
|
||||
|
||||
const setup = (propOverrides?: object) => {
|
||||
const props: Props = {
|
||||
@ -19,8 +18,6 @@ const setup = (propOverrides?: object) => {
|
||||
plugins: [] as PluginMeta[],
|
||||
searchQuery: '',
|
||||
setPluginsSearchQuery: mockToolkitActionCreator(setPluginsSearchQuery),
|
||||
setPluginsLayoutMode: mockToolkitActionCreator(setPluginsLayoutMode),
|
||||
layoutMode: LayoutModes.Grid,
|
||||
loadPlugins: jest.fn(),
|
||||
hasFetched: false,
|
||||
};
|
||||
|
@ -6,20 +6,17 @@ import OrgActionBar from 'app/core/components/OrgActionBar/OrgActionBar';
|
||||
import PluginList from './PluginList';
|
||||
import { loadPlugins } from './state/actions';
|
||||
import { getNavModel } from 'app/core/selectors/navModel';
|
||||
import { getLayoutMode, getPlugins, getPluginsSearchQuery } from './state/selectors';
|
||||
import { LayoutMode } from 'app/core/components/LayoutSelector/LayoutSelector';
|
||||
import { getPlugins, getPluginsSearchQuery } from './state/selectors';
|
||||
import { NavModel, PluginMeta } from '@grafana/data';
|
||||
import { StoreState } from 'app/types';
|
||||
import { setPluginsLayoutMode, setPluginsSearchQuery } from './state/reducers';
|
||||
import { setPluginsSearchQuery } from './state/reducers';
|
||||
|
||||
export interface Props {
|
||||
navModel: NavModel;
|
||||
plugins: PluginMeta[];
|
||||
layoutMode: LayoutMode;
|
||||
searchQuery: string;
|
||||
hasFetched: boolean;
|
||||
loadPlugins: typeof loadPlugins;
|
||||
setPluginsLayoutMode: typeof setPluginsLayoutMode;
|
||||
setPluginsSearchQuery: typeof setPluginsSearchQuery;
|
||||
}
|
||||
|
||||
@ -33,15 +30,7 @@ export class PluginListPage extends PureComponent<Props> {
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
hasFetched,
|
||||
navModel,
|
||||
plugins,
|
||||
layoutMode,
|
||||
setPluginsLayoutMode,
|
||||
setPluginsSearchQuery,
|
||||
searchQuery,
|
||||
} = this.props;
|
||||
const { hasFetched, navModel, plugins, setPluginsSearchQuery, searchQuery } = this.props;
|
||||
|
||||
const linkButton = {
|
||||
href: 'https://grafana.com/plugins?utm_source=grafana_plugin_list',
|
||||
@ -54,12 +43,10 @@ export class PluginListPage extends PureComponent<Props> {
|
||||
<>
|
||||
<OrgActionBar
|
||||
searchQuery={searchQuery}
|
||||
layoutMode={layoutMode}
|
||||
onSetLayoutMode={mode => setPluginsLayoutMode(mode)}
|
||||
setSearchQuery={query => setPluginsSearchQuery(query)}
|
||||
linkButton={linkButton}
|
||||
/>
|
||||
{hasFetched && plugins && plugins && <PluginList plugins={plugins} layoutMode={layoutMode} />}
|
||||
{hasFetched && plugins && plugins && <PluginList plugins={plugins} />}
|
||||
</>
|
||||
</Page.Contents>
|
||||
</Page>
|
||||
@ -71,7 +58,6 @@ function mapStateToProps(state: StoreState) {
|
||||
return {
|
||||
navModel: getNavModel(state.navIndex, 'plugins'),
|
||||
plugins: getPlugins(state.plugins),
|
||||
layoutMode: getLayoutMode(state.plugins),
|
||||
searchQuery: getPluginsSearchQuery(state.plugins),
|
||||
hasFetched: state.plugins.hasFetched,
|
||||
};
|
||||
@ -79,7 +65,6 @@ function mapStateToProps(state: StoreState) {
|
||||
|
||||
const mapDispatchToProps = {
|
||||
loadPlugins,
|
||||
setPluginsLayoutMode,
|
||||
setPluginsSearchQuery,
|
||||
};
|
||||
|
||||
|
106
public/app/features/plugins/PluginSignatureBadge.tsx
Normal file
106
public/app/features/plugins/PluginSignatureBadge.tsx
Normal file
@ -0,0 +1,106 @@
|
||||
import React from 'react';
|
||||
import { Icon, stylesFactory, useTheme, IconName, Tooltip } from '@grafana/ui';
|
||||
import { GrafanaTheme, PluginSignatureStatus, getColorFromHexRgbOrName } from '@grafana/data';
|
||||
import { css } from 'emotion';
|
||||
import tinycolor from 'tinycolor2';
|
||||
|
||||
interface Props {
|
||||
status: PluginSignatureStatus;
|
||||
}
|
||||
|
||||
export const PluginSignatureBadge: React.FC<Props> = ({ status }) => {
|
||||
const theme = useTheme();
|
||||
const display = getSignatureDisplayModel(status);
|
||||
const styles = getStyles(theme, display);
|
||||
|
||||
return (
|
||||
<Tooltip content={display.tooltip} placement="left">
|
||||
<div className={styles.wrapper}>
|
||||
<Icon name={display.icon} size="sm" />
|
||||
<span>{display.text}</span>
|
||||
</div>
|
||||
</Tooltip>
|
||||
);
|
||||
};
|
||||
|
||||
interface DisplayModel {
|
||||
text: string;
|
||||
icon: IconName;
|
||||
color: string;
|
||||
tooltip: string;
|
||||
}
|
||||
|
||||
function getSignatureDisplayModel(signature: PluginSignatureStatus): DisplayModel {
|
||||
switch (signature) {
|
||||
case PluginSignatureStatus.internal:
|
||||
return { text: 'Core', icon: 'cube', color: 'blue', tooltip: 'Core plugin that is bundled with Grafana' };
|
||||
case PluginSignatureStatus.valid:
|
||||
return { text: 'Signed', icon: 'lock', color: 'green', tooltip: 'Signed and verified plugin' };
|
||||
case PluginSignatureStatus.invalid:
|
||||
return {
|
||||
text: 'Invalid',
|
||||
icon: 'exclamation-triangle',
|
||||
color: 'red',
|
||||
tooltip: 'Invalid plugin signature',
|
||||
};
|
||||
case PluginSignatureStatus.modified:
|
||||
return {
|
||||
text: 'Modified',
|
||||
icon: 'exclamation-triangle',
|
||||
color: 'red',
|
||||
tooltip: 'Valid signature but content has been modified',
|
||||
};
|
||||
}
|
||||
|
||||
return { text: 'Unsigned', icon: 'exclamation-triangle', color: 'red', tooltip: 'Unsigned external plugin' };
|
||||
}
|
||||
|
||||
const getStyles = stylesFactory((theme: GrafanaTheme, model: DisplayModel) => {
|
||||
let sourceColor = getColorFromHexRgbOrName(model.color);
|
||||
let borderColor = '';
|
||||
let bgColor = '';
|
||||
let textColor = '';
|
||||
|
||||
if (theme.isDark) {
|
||||
bgColor = tinycolor(sourceColor)
|
||||
.darken(38)
|
||||
.toString();
|
||||
borderColor = tinycolor(sourceColor)
|
||||
.darken(25)
|
||||
.toString();
|
||||
textColor = tinycolor(sourceColor)
|
||||
.lighten(45)
|
||||
.toString();
|
||||
} else {
|
||||
bgColor = tinycolor(sourceColor)
|
||||
.lighten(30)
|
||||
.toString();
|
||||
borderColor = tinycolor(sourceColor)
|
||||
.lighten(15)
|
||||
.toString();
|
||||
textColor = tinycolor(sourceColor)
|
||||
.darken(40)
|
||||
.toString();
|
||||
}
|
||||
|
||||
return {
|
||||
wrapper: css`
|
||||
font-size: ${theme.typography.size.sm};
|
||||
display: inline-flex;
|
||||
padding: 1px 4px;
|
||||
border-radius: 3px;
|
||||
margin-top: 6px;
|
||||
background: ${bgColor};
|
||||
border: 1px solid ${borderColor};
|
||||
color: ${textColor};
|
||||
|
||||
> span {
|
||||
position: relative;
|
||||
top: 1px;
|
||||
margin-left: 2px;
|
||||
}
|
||||
`,
|
||||
};
|
||||
});
|
||||
|
||||
PluginSignatureBadge.displayName = 'PluginSignatureBadge';
|
@ -2,7 +2,7 @@
|
||||
|
||||
exports[`Render should render component 1`] = `
|
||||
<section
|
||||
className="card-section card-list-layout-grid"
|
||||
className="card-section card-list-layout-list"
|
||||
>
|
||||
<ol
|
||||
className="card-list"
|
||||
|
@ -16,6 +16,7 @@ exports[`Render should render component 1`] = `
|
||||
>
|
||||
panel
|
||||
</div>
|
||||
<PluginSignatureBadge />
|
||||
</div>
|
||||
<div
|
||||
className="card-item-body"
|
||||
@ -62,6 +63,7 @@ exports[`Render should render has plugin section 1`] = `
|
||||
>
|
||||
panel
|
||||
</div>
|
||||
<PluginSignatureBadge />
|
||||
<div
|
||||
className="card-item-notice"
|
||||
>
|
||||
|
@ -17,14 +17,12 @@ exports[`Render should render component 1`] = `
|
||||
isLoading={true}
|
||||
>
|
||||
<OrgActionBar
|
||||
layoutMode="grid"
|
||||
linkButton={
|
||||
Object {
|
||||
"href": "https://grafana.com/plugins?utm_source=grafana_plugin_list",
|
||||
"title": "Find more plugins on Grafana.com",
|
||||
}
|
||||
}
|
||||
onSetLayoutMode={[Function]}
|
||||
searchQuery=""
|
||||
setSearchQuery={[Function]}
|
||||
/>
|
||||
@ -49,19 +47,16 @@ exports[`Render should render list 1`] = `
|
||||
isLoading={false}
|
||||
>
|
||||
<OrgActionBar
|
||||
layoutMode="grid"
|
||||
linkButton={
|
||||
Object {
|
||||
"href": "https://grafana.com/plugins?utm_source=grafana_plugin_list",
|
||||
"title": "Find more plugins on Grafana.com",
|
||||
}
|
||||
}
|
||||
onSetLayoutMode={[Function]}
|
||||
searchQuery=""
|
||||
setSearchQuery={[Function]}
|
||||
/>
|
||||
<PluginList
|
||||
layoutMode="grid"
|
||||
plugins={Array []}
|
||||
/>
|
||||
</PageContents>
|
||||
|
@ -6,11 +6,9 @@ import {
|
||||
pluginDashboardsLoaded,
|
||||
pluginsLoaded,
|
||||
pluginsReducer,
|
||||
setPluginsLayoutMode,
|
||||
setPluginsSearchQuery,
|
||||
} from './reducers';
|
||||
import { PluginMetaInfo, PluginType } from '@grafana/data';
|
||||
import { LayoutModes } from '../../../core/components/LayoutSelector/LayoutSelector';
|
||||
|
||||
describe('pluginsReducer', () => {
|
||||
describe('when pluginsLoaded is dispatched', () => {
|
||||
@ -58,18 +56,6 @@ describe('pluginsReducer', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('when setPluginsLayoutMode is dispatched', () => {
|
||||
it('then state should be correct', () => {
|
||||
reducerTester<PluginsState>()
|
||||
.givenReducer(pluginsReducer, { ...initialState })
|
||||
.whenActionIsDispatched(setPluginsLayoutMode(LayoutModes.List))
|
||||
.thenStateShouldEqual({
|
||||
...initialState,
|
||||
layoutMode: LayoutModes.List,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('when pluginDashboardsLoad is dispatched', () => {
|
||||
it('then state should be correct', () => {
|
||||
reducerTester<PluginsState>()
|
||||
|
@ -1,13 +1,11 @@
|
||||
import { createSlice, PayloadAction } from '@reduxjs/toolkit';
|
||||
import { PluginMeta, PanelPlugin } from '@grafana/data';
|
||||
import { PluginsState } from 'app/types';
|
||||
import { LayoutMode, LayoutModes } from '../../../core/components/LayoutSelector/LayoutSelector';
|
||||
import { PluginDashboard } from '../../../types/plugins';
|
||||
|
||||
export const initialState: PluginsState = {
|
||||
plugins: [],
|
||||
searchQuery: '',
|
||||
layoutMode: LayoutModes.Grid,
|
||||
hasFetched: false,
|
||||
dashboards: [],
|
||||
isLoadingPluginDashboards: false,
|
||||
@ -25,9 +23,6 @@ const pluginsSlice = createSlice({
|
||||
setPluginsSearchQuery: (state, action: PayloadAction<string>) => {
|
||||
state.searchQuery = action.payload;
|
||||
},
|
||||
setPluginsLayoutMode: (state, action: PayloadAction<LayoutMode>) => {
|
||||
state.layoutMode = action.payload;
|
||||
},
|
||||
pluginDashboardsLoad: (state, action: PayloadAction<undefined>) => {
|
||||
state.isLoadingPluginDashboards = true;
|
||||
state.dashboards = [];
|
||||
@ -46,7 +41,6 @@ export const {
|
||||
pluginsLoaded,
|
||||
pluginDashboardsLoad,
|
||||
pluginDashboardsLoaded,
|
||||
setPluginsLayoutMode,
|
||||
setPluginsSearchQuery,
|
||||
panelPluginLoaded,
|
||||
} = pluginsSlice.actions;
|
||||
|
@ -9,4 +9,3 @@ export const getPlugins = (state: PluginsState) => {
|
||||
};
|
||||
|
||||
export const getPluginsSearchQuery = (state: PluginsState) => state.searchQuery;
|
||||
export const getLayoutMode = (state: PluginsState) => state.layoutMode;
|
||||
|
@ -24,7 +24,6 @@ export interface PanelPluginsIndex {
|
||||
export interface PluginsState {
|
||||
plugins: PluginMeta[];
|
||||
searchQuery: string;
|
||||
layoutMode: string;
|
||||
hasFetched: boolean;
|
||||
dashboards: PluginDashboard[];
|
||||
isLoadingPluginDashboards: boolean;
|
||||
|
@ -143,9 +143,9 @@ $code-tag-bg: $dark-1;
|
||||
$code-tag-border: $dark-9;
|
||||
|
||||
// cards
|
||||
$card-background: linear-gradient(135deg, $dark-4, $dark-3);
|
||||
$card-background-hover: linear-gradient(135deg, $dark-5, $dark-6);
|
||||
$card-shadow: -1px -1px 0 0 hsla(0, 0%, 100%, 0.1), 1px 1px 0 0 rgba(0, 0, 0, 0.3);
|
||||
$card-background: #202226;
|
||||
$card-background-hover: #25272b;
|
||||
$card-shadow: none;
|
||||
|
||||
// Lists
|
||||
$list-item-bg: $card-background;
|
||||
|
@ -136,9 +136,9 @@ $code-tag-bg: $gray-6;
|
||||
$code-tag-border: $gray-4;
|
||||
|
||||
// cards
|
||||
$card-background: linear-gradient(135deg, $gray-6, $gray-7);
|
||||
$card-background-hover: linear-gradient(135deg, $gray-6, $gray-5);
|
||||
$card-shadow: -1px -1px 0 0 hsla(0, 0%, 100%, 0.1), 1px 1px 0 0 rgba(0, 0, 0, 0.1);
|
||||
$card-background: #f1f5f9;
|
||||
$card-background-hover: #eaf0f6;
|
||||
$card-shadow: none;
|
||||
|
||||
// Lists
|
||||
$list-item-bg: $gray-7;
|
||||
|
@ -24,7 +24,6 @@
|
||||
display: flex;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
box-shadow: $card-shadow;
|
||||
background: $panel-editor-viz-item-bg;
|
||||
border: 1px solid transparent;
|
||||
border-radius: 3px;
|
||||
|
Loading…
Reference in New Issue
Block a user