mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
IconName: Move to grafana/data and use type on NavModelItem (#55013)
* IconName: Allow strings * Moving to grafana/data and adding type to NavModelItem * Removed any type * ts fix
This commit is contained in:
parent
64869e3d90
commit
d59bb1e4c2
@ -6,7 +6,7 @@ import { DataSourceInstanceSettings } from './datasource';
|
|||||||
import { FeatureToggles } from './featureToggles.gen';
|
import { FeatureToggles } from './featureToggles.gen';
|
||||||
import { PanelPluginMeta } from './panel';
|
import { PanelPluginMeta } from './panel';
|
||||||
|
|
||||||
import { GrafanaTheme, NavLinkDTO, OrgRole } from '.';
|
import { GrafanaTheme, IconName, NavLinkDTO, OrgRole } from '.';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Describes the build information that will be available via the Grafana configuration.
|
* Describes the build information that will be available via the Grafana configuration.
|
||||||
@ -104,7 +104,7 @@ export type OAuth =
|
|||||||
*
|
*
|
||||||
* @public
|
* @public
|
||||||
*/
|
*/
|
||||||
export type OAuthSettings = Partial<Record<OAuth, { name: string; icon?: string }>>;
|
export type OAuthSettings = Partial<Record<OAuth, { name: string; icon?: IconName }>>;
|
||||||
|
|
||||||
/** Current user info included in bootData
|
/** Current user info included in bootData
|
||||||
*
|
*
|
||||||
|
205
packages/grafana-data/src/types/icon.ts
Normal file
205
packages/grafana-data/src/types/icon.ts
Normal file
@ -0,0 +1,205 @@
|
|||||||
|
export const availableIconsIndex = {
|
||||||
|
google: true,
|
||||||
|
microsoft: true,
|
||||||
|
github: true,
|
||||||
|
gitlab: true,
|
||||||
|
okta: true,
|
||||||
|
anchor: true,
|
||||||
|
'angle-double-down': true,
|
||||||
|
'angle-double-right': true,
|
||||||
|
'angle-double-up': true,
|
||||||
|
'angle-down': true,
|
||||||
|
'angle-left': true,
|
||||||
|
'angle-right': true,
|
||||||
|
'angle-up': true,
|
||||||
|
apps: true,
|
||||||
|
arrow: true,
|
||||||
|
'arrow-down': true,
|
||||||
|
'arrow-from-right': true,
|
||||||
|
'arrow-left': true,
|
||||||
|
'arrow-random': true,
|
||||||
|
'arrow-right': true,
|
||||||
|
'arrow-up': true,
|
||||||
|
'arrows-h': true,
|
||||||
|
'arrows-v': true,
|
||||||
|
backward: true,
|
||||||
|
bars: true,
|
||||||
|
bell: true,
|
||||||
|
'bell-slash': true,
|
||||||
|
bolt: true,
|
||||||
|
book: true,
|
||||||
|
bookmark: true,
|
||||||
|
'book-open': true,
|
||||||
|
'brackets-curly': true,
|
||||||
|
bug: true,
|
||||||
|
building: true,
|
||||||
|
'calculator-alt': true,
|
||||||
|
'calendar-alt': true,
|
||||||
|
camera: true,
|
||||||
|
capture: true,
|
||||||
|
'channel-add': true,
|
||||||
|
'chart-line': true,
|
||||||
|
check: true,
|
||||||
|
'check-circle': true,
|
||||||
|
'check-square': true,
|
||||||
|
circle: true,
|
||||||
|
'clipboard-alt': true,
|
||||||
|
'clock-nine': true,
|
||||||
|
cloud: true,
|
||||||
|
'cloud-download': true,
|
||||||
|
'cloud-upload': true,
|
||||||
|
'code-branch': true,
|
||||||
|
cog: true,
|
||||||
|
columns: true,
|
||||||
|
'comment-alt': true,
|
||||||
|
'comment-alt-message': true,
|
||||||
|
'comment-alt-share': true,
|
||||||
|
'comments-alt': true,
|
||||||
|
compass: true,
|
||||||
|
copy: true,
|
||||||
|
'credit-card': true,
|
||||||
|
cube: true,
|
||||||
|
dashboard: true,
|
||||||
|
database: true,
|
||||||
|
'document-info': true,
|
||||||
|
'download-alt': true,
|
||||||
|
draggabledots: true,
|
||||||
|
edit: true,
|
||||||
|
'ellipsis-v': true,
|
||||||
|
envelope: true,
|
||||||
|
'exchange-alt': true,
|
||||||
|
'exclamation-triangle': true,
|
||||||
|
'exclamation-circle': true,
|
||||||
|
'external-link-alt': true,
|
||||||
|
eye: true,
|
||||||
|
'eye-slash': true,
|
||||||
|
'ellipsis-h': true,
|
||||||
|
'fa fa-spinner': true,
|
||||||
|
favorite: true,
|
||||||
|
'file-alt': true,
|
||||||
|
'file-blank': true,
|
||||||
|
'file-copy-alt': true,
|
||||||
|
filter: true,
|
||||||
|
folder: true,
|
||||||
|
font: true,
|
||||||
|
fire: true,
|
||||||
|
'folder-open': true,
|
||||||
|
'folder-plus': true,
|
||||||
|
'folder-upload': true,
|
||||||
|
forward: true,
|
||||||
|
'gf-bar-alignment-after': true,
|
||||||
|
'gf-bar-alignment-before': true,
|
||||||
|
'gf-bar-alignment-center': true,
|
||||||
|
'gf-glue': true,
|
||||||
|
'gf-grid': true,
|
||||||
|
'gf-interpolation-linear': true,
|
||||||
|
'gf-interpolation-smooth': true,
|
||||||
|
'gf-interpolation-step-after': true,
|
||||||
|
'gf-interpolation-step-before': true,
|
||||||
|
'gf-landscape': true,
|
||||||
|
'gf-layout-simple': true,
|
||||||
|
'gf-logs': true,
|
||||||
|
'gf-portrait': true,
|
||||||
|
'gf-service-account': true,
|
||||||
|
grafana: true,
|
||||||
|
'graph-bar': true,
|
||||||
|
heart: true,
|
||||||
|
'heart-break': true,
|
||||||
|
history: true,
|
||||||
|
home: true,
|
||||||
|
'home-alt': true,
|
||||||
|
'horizontal-align-center': true,
|
||||||
|
'horizontal-align-left': true,
|
||||||
|
'horizontal-align-right': true,
|
||||||
|
hourglass: true,
|
||||||
|
import: true,
|
||||||
|
info: true,
|
||||||
|
'info-circle': true,
|
||||||
|
'key-skeleton-alt': true,
|
||||||
|
keyboard: true,
|
||||||
|
'layer-group': true,
|
||||||
|
'library-panel': true,
|
||||||
|
'line-alt': true,
|
||||||
|
link: true,
|
||||||
|
'list-ui-alt': true,
|
||||||
|
'list-ul': true,
|
||||||
|
lock: true,
|
||||||
|
'map-marker': true,
|
||||||
|
message: true,
|
||||||
|
minus: true,
|
||||||
|
'minus-circle': true,
|
||||||
|
'mobile-android': true,
|
||||||
|
monitor: true,
|
||||||
|
palette: true,
|
||||||
|
'panel-add': true,
|
||||||
|
pause: true,
|
||||||
|
pen: true,
|
||||||
|
percentage: true,
|
||||||
|
play: true,
|
||||||
|
plug: true,
|
||||||
|
plus: true,
|
||||||
|
'plus-circle': true,
|
||||||
|
'plus-square': true,
|
||||||
|
power: true,
|
||||||
|
'presentation-play': true,
|
||||||
|
process: true,
|
||||||
|
'question-circle': true,
|
||||||
|
'record-audio': true,
|
||||||
|
repeat: true,
|
||||||
|
rocket: true,
|
||||||
|
'ruler-combined': true,
|
||||||
|
save: true,
|
||||||
|
search: true,
|
||||||
|
'search-minus': true,
|
||||||
|
'search-plus': true,
|
||||||
|
'share-alt': true,
|
||||||
|
shield: true,
|
||||||
|
'shield-exclamation': true,
|
||||||
|
signal: true,
|
||||||
|
signin: true,
|
||||||
|
signout: true,
|
||||||
|
sitemap: true,
|
||||||
|
slack: true,
|
||||||
|
'sliders-v-alt': true,
|
||||||
|
'sort-amount-down': true,
|
||||||
|
'sort-amount-up': true,
|
||||||
|
'square-shape': true,
|
||||||
|
star: true,
|
||||||
|
'step-backward': true,
|
||||||
|
'stopwatch-slash': true,
|
||||||
|
sync: true,
|
||||||
|
table: true,
|
||||||
|
'tag-alt': true,
|
||||||
|
'text-fields': true,
|
||||||
|
times: true,
|
||||||
|
'toggle-on': true,
|
||||||
|
'trash-alt': true,
|
||||||
|
unlock: true,
|
||||||
|
upload: true,
|
||||||
|
user: true,
|
||||||
|
'users-alt': true,
|
||||||
|
'vertical-align-bottom': true,
|
||||||
|
'vertical-align-center': true,
|
||||||
|
'vertical-align-top': true,
|
||||||
|
'wrap-text': true,
|
||||||
|
rss: true,
|
||||||
|
x: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
export type IconName = keyof typeof availableIconsIndex;
|
||||||
|
|
||||||
|
export function isIconName(iconName: unknown): iconName is IconName {
|
||||||
|
if (!iconName || typeof iconName !== 'string') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return iconName in availableIconsIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function toIconName(iconName: string): IconName | undefined {
|
||||||
|
if (isIconName(iconName)) {
|
||||||
|
return iconName;
|
||||||
|
}
|
||||||
|
|
||||||
|
return undefined;
|
||||||
|
}
|
@ -50,3 +50,4 @@ export type { FeatureToggles } from './featureToggles.gen';
|
|||||||
export * from './alerts';
|
export * from './alerts';
|
||||||
export * from './slider';
|
export * from './slider';
|
||||||
export * from './accesscontrol';
|
export * from './accesscontrol';
|
||||||
|
export * from './icon';
|
||||||
|
@ -1,12 +1,14 @@
|
|||||||
import { ComponentType } from 'react';
|
import { ComponentType } from 'react';
|
||||||
|
|
||||||
|
import { IconName } from './icon';
|
||||||
|
|
||||||
export interface NavLinkDTO {
|
export interface NavLinkDTO {
|
||||||
id?: string;
|
id?: string;
|
||||||
text: string;
|
text: string;
|
||||||
description?: string;
|
description?: string;
|
||||||
section?: NavSection;
|
section?: NavSection;
|
||||||
subTitle?: string;
|
subTitle?: string;
|
||||||
icon?: string;
|
icon?: IconName;
|
||||||
img?: string;
|
img?: string;
|
||||||
url?: string;
|
url?: string;
|
||||||
target?: string;
|
target?: string;
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import { ComponentType } from 'react';
|
import { ComponentType } from 'react';
|
||||||
|
|
||||||
import { KeyValue } from './data';
|
import { KeyValue } from './data';
|
||||||
|
import { IconName } from './icon';
|
||||||
|
|
||||||
/** Describes plugins life cycle status */
|
/** Describes plugins life cycle status */
|
||||||
export enum PluginState {
|
export enum PluginState {
|
||||||
@ -158,7 +159,7 @@ export interface PluginConfigPageProps<T extends PluginMeta> {
|
|||||||
|
|
||||||
export interface PluginConfigPage<T extends PluginMeta> {
|
export interface PluginConfigPage<T extends PluginMeta> {
|
||||||
title: string; // Display
|
title: string; // Display
|
||||||
icon?: string;
|
icon?: IconName;
|
||||||
id: string; // Unique, in URL
|
id: string; // Unique, in URL
|
||||||
|
|
||||||
body: ComponentType<PluginConfigPageProps<T>>;
|
body: ComponentType<PluginConfigPageProps<T>>;
|
||||||
|
@ -2,10 +2,9 @@ import { css, cx } from '@emotion/css';
|
|||||||
import { uniqueId } from 'lodash';
|
import { uniqueId } from 'lodash';
|
||||||
import React, { useCallback, useEffect, useRef } from 'react';
|
import React, { useCallback, useEffect, useRef } from 'react';
|
||||||
|
|
||||||
import { GrafanaTheme2, SelectableValue } from '@grafana/data';
|
import { GrafanaTheme2, SelectableValue, toIconName } from '@grafana/data';
|
||||||
|
|
||||||
import { useStyles2 } from '../../../themes';
|
import { useStyles2 } from '../../../themes';
|
||||||
import { toIconName } from '../../../types/icon';
|
|
||||||
import { Icon } from '../../Icon/Icon';
|
import { Icon } from '../../Icon/Icon';
|
||||||
|
|
||||||
import { RadioButtonSize, RadioButton } from './RadioButton';
|
import { RadioButtonSize, RadioButton } from './RadioButton';
|
||||||
@ -74,6 +73,7 @@ export function RadioButtonGroup<T>({
|
|||||||
{options.map((opt, i) => {
|
{options.map((opt, i) => {
|
||||||
const isItemDisabled = disabledOptions && opt.value && disabledOptions.includes(opt.value);
|
const isItemDisabled = disabledOptions && opt.value && disabledOptions.includes(opt.value);
|
||||||
const icon = opt.icon ? toIconName(opt.icon) : undefined;
|
const icon = opt.icon ? toIconName(opt.icon) : undefined;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<RadioButton
|
<RadioButton
|
||||||
size={size}
|
size={size}
|
||||||
|
@ -2,10 +2,11 @@ import { css } from '@emotion/css';
|
|||||||
import { ComponentMeta } from '@storybook/react';
|
import { ComponentMeta } from '@storybook/react';
|
||||||
import React, { ChangeEvent, useState } from 'react';
|
import React, { ChangeEvent, useState } from 'react';
|
||||||
|
|
||||||
|
import { toIconName, IconName } from '@grafana/data';
|
||||||
import { Input, Field, Icon } from '@grafana/ui';
|
import { Input, Field, Icon } from '@grafana/ui';
|
||||||
|
|
||||||
import { useTheme2 } from '../../themes';
|
import { useTheme2 } from '../../themes';
|
||||||
import { getAvailableIcons, IconName } from '../../types';
|
import { getAvailableIcons } from '../../types';
|
||||||
import { withCenteredStory } from '../../utils/storybook/withCenteredStory';
|
import { withCenteredStory } from '../../utils/storybook/withCenteredStory';
|
||||||
|
|
||||||
import mdx from './Icon.mdx';
|
import mdx from './Icon.mdx';
|
||||||
@ -90,7 +91,7 @@ export const IconsOverview = () => {
|
|||||||
{icons
|
{icons
|
||||||
.filter((val) => val.includes(filter))
|
.filter((val) => val.includes(filter))
|
||||||
.map((i) => {
|
.map((i) => {
|
||||||
return <IconWrapper name={i} key={i} />;
|
return <IconWrapper name={toIconName(i)!} key={i} />;
|
||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -2,11 +2,10 @@ import { css, cx } from '@emotion/css';
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import SVG from 'react-inlinesvg';
|
import SVG from 'react-inlinesvg';
|
||||||
|
|
||||||
import { GrafanaTheme2 } from '@grafana/data';
|
import { GrafanaTheme2, isIconName } from '@grafana/data';
|
||||||
|
|
||||||
import { useStyles2 } from '../../themes/ThemeContext';
|
import { useStyles2 } from '../../themes/ThemeContext';
|
||||||
import { IconName, IconType, IconSize } from '../../types/icon';
|
import { IconName, IconType, IconSize } from '../../types/icon';
|
||||||
export { toIconName } from '../../types/icon';
|
|
||||||
|
|
||||||
import { cacheInitialized, initIconCache, iconRoot } from './iconBundle';
|
import { cacheInitialized, initIconCache, iconRoot } from './iconBundle';
|
||||||
import { getIconSubDir, getSvgSize } from './utils';
|
import { getIconSubDir, getSvgSize } from './utils';
|
||||||
@ -54,6 +53,10 @@ export const Icon = React.forwardRef<HTMLDivElement, IconProps>(
|
|||||||
initIconCache();
|
initIconCache();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!isIconName(name)) {
|
||||||
|
console.warn('Icon component passed an invalid icon name', name);
|
||||||
|
}
|
||||||
|
|
||||||
const svgSize = getSvgSize(size);
|
const svgSize = getSvgSize(size);
|
||||||
const svgHgt = svgSize;
|
const svgHgt = svgSize;
|
||||||
const svgWid = name?.startsWith('gf-bar-align') ? 16 : name?.startsWith('gf-interp') ? 30 : svgSize;
|
const svgWid = name?.startsWith('gf-bar-align') ? 16 : name?.startsWith('gf-interp') ? 30 : svgSize;
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
import { Icon, toIconName } from '../Icon/Icon';
|
import { toIconName } from '@grafana/data';
|
||||||
|
|
||||||
|
import { Icon } from '../Icon/Icon';
|
||||||
|
|
||||||
export function parseAccessory(prefix: string | undefined) {
|
export function parseAccessory(prefix: string | undefined) {
|
||||||
const icon = prefix && prefix.match(/icon-/g) && toIconName(prefix.replace(/icon-/g, ''));
|
const icon = prefix && prefix.match(/icon-/g) && toIconName(prefix.replace(/icon-/g, ''));
|
||||||
|
@ -3,10 +3,10 @@ import { action } from '@storybook/addon-actions';
|
|||||||
import { Meta, Story } from '@storybook/react';
|
import { Meta, Story } from '@storybook/react';
|
||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
|
|
||||||
import { SelectableValue } from '@grafana/data';
|
import { SelectableValue, toIconName } from '@grafana/data';
|
||||||
import { Icon, Select, AsyncSelect, MultiSelect, AsyncMultiSelect } from '@grafana/ui';
|
import { Icon, Select, AsyncSelect, MultiSelect, AsyncMultiSelect } from '@grafana/ui';
|
||||||
|
|
||||||
import { getAvailableIcons, toIconName } from '../../types';
|
import { getAvailableIcons } from '../../types';
|
||||||
import { withCenteredStory, withHorizontallyCenteredStory } from '../../utils/storybook/withCenteredStory';
|
import { withCenteredStory, withHorizontallyCenteredStory } from '../../utils/storybook/withCenteredStory';
|
||||||
|
|
||||||
import mdx from './Select.mdx';
|
import mdx from './Select.mdx';
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
import { cx } from '@emotion/css';
|
import { cx } from '@emotion/css';
|
||||||
import React, { FC, RefCallback } from 'react';
|
import React, { FC, RefCallback } from 'react';
|
||||||
|
|
||||||
import { SelectableValue } from '@grafana/data';
|
import { SelectableValue, toIconName } from '@grafana/data';
|
||||||
|
|
||||||
import { useTheme2 } from '../../themes/ThemeContext';
|
import { useTheme2 } from '../../themes/ThemeContext';
|
||||||
import { CustomScrollbar } from '../CustomScrollbar/CustomScrollbar';
|
import { CustomScrollbar } from '../CustomScrollbar/CustomScrollbar';
|
||||||
import { Icon, toIconName } from '../Icon/Icon';
|
import { Icon } from '../Icon/Icon';
|
||||||
|
|
||||||
import { getSelectStyles } from './getSelectStyles';
|
import { getSelectStyles } from './getSelectStyles';
|
||||||
|
|
||||||
|
@ -1,13 +1,11 @@
|
|||||||
import { cx, css } from '@emotion/css';
|
import { cx, css } from '@emotion/css';
|
||||||
import { isString } from 'lodash';
|
|
||||||
import React, { forwardRef, ButtonHTMLAttributes } from 'react';
|
import React, { forwardRef, ButtonHTMLAttributes } from 'react';
|
||||||
|
|
||||||
import { GrafanaTheme2 } from '@grafana/data';
|
import { GrafanaTheme2, IconName, isIconName } from '@grafana/data';
|
||||||
import { selectors } from '@grafana/e2e-selectors';
|
import { selectors } from '@grafana/e2e-selectors';
|
||||||
|
|
||||||
import { styleMixins, useStyles2 } from '../../themes';
|
import { styleMixins, useStyles2 } from '../../themes';
|
||||||
import { getFocusStyles, getMouseFocusStyles } from '../../themes/mixins';
|
import { getFocusStyles, getMouseFocusStyles } from '../../themes/mixins';
|
||||||
import { IconName, toIconName } from '../../types/icon';
|
|
||||||
import { getPropertiesForVariant } from '../Button';
|
import { getPropertiesForVariant } from '../Button';
|
||||||
import { Icon } from '../Icon/Icon';
|
import { Icon } from '../Icon/Icon';
|
||||||
import { Tooltip } from '../Tooltip/Tooltip';
|
import { Tooltip } from '../Tooltip/Tooltip';
|
||||||
@ -115,10 +113,8 @@ function renderIcon(icon: IconName | React.ReactNode) {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const iconName = isString(icon) && toIconName(icon);
|
if (isIconName(icon)) {
|
||||||
|
return <Icon name={icon} size="lg" />;
|
||||||
if (iconName) {
|
|
||||||
return <Icon name={iconName} size="lg" />;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return icon;
|
return icon;
|
||||||
|
@ -6,7 +6,7 @@ import { AsyncSelect, Select } from './Forms/Legacy/Select/Select';
|
|||||||
import { Switch } from './Forms/Legacy/Switch/Switch';
|
import { Switch } from './Forms/Legacy/Switch/Switch';
|
||||||
import { SecretFormField } from './SecretFormField/SecretFormField';
|
import { SecretFormField } from './SecretFormField/SecretFormField';
|
||||||
|
|
||||||
export { Icon, toIconName } from './Icon/Icon';
|
export { Icon } from './Icon/Icon';
|
||||||
export { IconButton, type IconButtonVariant } from './IconButton/IconButton';
|
export { IconButton, type IconButtonVariant } from './IconButton/IconButton';
|
||||||
export { ConfirmButton } from './ConfirmButton/ConfirmButton';
|
export { ConfirmButton } from './ConfirmButton/ConfirmButton';
|
||||||
export { DeleteButton } from './ConfirmButton/DeleteButton';
|
export { DeleteButton } from './ConfirmButton/DeleteButton';
|
||||||
|
@ -1,207 +1,16 @@
|
|||||||
import { Field, FieldType } from '@grafana/data';
|
import { availableIconsIndex, Field, FieldType, IconName } from '@grafana/data';
|
||||||
|
|
||||||
import { ComponentSize } from './size';
|
import { ComponentSize } from './size';
|
||||||
|
|
||||||
|
// Exported from here for backwards compatibility
|
||||||
|
export type { IconName } from '@grafana/data';
|
||||||
|
export { toIconName } from '@grafana/data';
|
||||||
|
|
||||||
export type IconType = 'mono' | 'default' | 'solid';
|
export type IconType = 'mono' | 'default' | 'solid';
|
||||||
export type IconSize = ComponentSize | 'xl' | 'xxl' | 'xxxl';
|
export type IconSize = ComponentSize | 'xl' | 'xxl' | 'xxxl';
|
||||||
|
|
||||||
const avaibleBrandIcons = [
|
|
||||||
'google' as const,
|
|
||||||
'microsoft' as const,
|
|
||||||
'github' as const,
|
|
||||||
'gitlab' as const,
|
|
||||||
'okta' as const,
|
|
||||||
];
|
|
||||||
|
|
||||||
const availableIcons = [
|
|
||||||
'anchor' as const,
|
|
||||||
'angle-double-down' as const,
|
|
||||||
'angle-double-right' as const,
|
|
||||||
'angle-double-up' as const,
|
|
||||||
'angle-down' as const,
|
|
||||||
'angle-left' as const,
|
|
||||||
'angle-right' as const,
|
|
||||||
'angle-up' as const,
|
|
||||||
'apps' as const,
|
|
||||||
'arrow' as const,
|
|
||||||
'arrow-down' as const,
|
|
||||||
'arrow-from-right' as const,
|
|
||||||
'arrow-left' as const,
|
|
||||||
'arrow-random' as const,
|
|
||||||
'arrow-right' as const,
|
|
||||||
'arrow-up' as const,
|
|
||||||
'arrows-h' as const,
|
|
||||||
'arrows-v' as const,
|
|
||||||
'backward' as const,
|
|
||||||
'bars' as const,
|
|
||||||
'bell' as const,
|
|
||||||
'bell-slash' as const,
|
|
||||||
'bolt' as const,
|
|
||||||
'book' as const,
|
|
||||||
'bookmark' as const,
|
|
||||||
'book-open' as const,
|
|
||||||
'brackets-curly' as const,
|
|
||||||
'building' as const,
|
|
||||||
'bug' as const,
|
|
||||||
'building' as const,
|
|
||||||
'calculator-alt' as const,
|
|
||||||
'calendar-alt' as const,
|
|
||||||
'camera' as const,
|
|
||||||
'capture' as const,
|
|
||||||
'channel-add' as const,
|
|
||||||
'chart-line' as const,
|
|
||||||
'check' as const,
|
|
||||||
'check-circle' as const,
|
|
||||||
'check-square' as const,
|
|
||||||
'circle' as const,
|
|
||||||
'clipboard-alt' as const,
|
|
||||||
'clock-nine' as const,
|
|
||||||
'cloud' as const,
|
|
||||||
'cloud-download' as const,
|
|
||||||
'cloud-upload' as const,
|
|
||||||
'code-branch' as const,
|
|
||||||
'cog' as const,
|
|
||||||
'columns' as const,
|
|
||||||
'comment-alt' as const,
|
|
||||||
'comment-alt-message' as const,
|
|
||||||
'comment-alt-share' as const,
|
|
||||||
'comments-alt' as const,
|
|
||||||
'compass' as const,
|
|
||||||
'copy' as const,
|
|
||||||
'credit-card' as const,
|
|
||||||
'cube' as const,
|
|
||||||
'dashboard' as const,
|
|
||||||
'database' as const,
|
|
||||||
'document-info' as const,
|
|
||||||
'download-alt' as const,
|
|
||||||
'draggabledots' as const,
|
|
||||||
'edit' as const,
|
|
||||||
'ellipsis-v' as const,
|
|
||||||
'envelope' as const,
|
|
||||||
'exchange-alt' as const,
|
|
||||||
'exclamation-triangle' as const,
|
|
||||||
'exclamation-circle' as const,
|
|
||||||
'external-link-alt' as const,
|
|
||||||
'eye' as const,
|
|
||||||
'eye-slash' as const,
|
|
||||||
'ellipsis-h' as const,
|
|
||||||
'fa fa-spinner' as const,
|
|
||||||
'favorite' as const,
|
|
||||||
'file-alt' as const,
|
|
||||||
'file-blank' as const,
|
|
||||||
'file-copy-alt' as const,
|
|
||||||
'filter' as const,
|
|
||||||
'folder' as const,
|
|
||||||
'font' as const,
|
|
||||||
'fire' as const,
|
|
||||||
'folder-open' as const,
|
|
||||||
'folder-plus' as const,
|
|
||||||
'folder-upload' as const,
|
|
||||||
'forward' as const,
|
|
||||||
'gf-bar-alignment-after' as const,
|
|
||||||
'gf-bar-alignment-before' as const,
|
|
||||||
'gf-bar-alignment-center' as const,
|
|
||||||
'gf-glue' as const,
|
|
||||||
'gf-grid' as const,
|
|
||||||
'gf-interpolation-linear' as const,
|
|
||||||
'gf-interpolation-smooth' as const,
|
|
||||||
'gf-interpolation-step-after' as const,
|
|
||||||
'gf-interpolation-step-before' as const,
|
|
||||||
'gf-landscape' as const,
|
|
||||||
'gf-layout-simple' as const,
|
|
||||||
'gf-logs' as const,
|
|
||||||
'gf-portrait' as const,
|
|
||||||
'grafana' as const,
|
|
||||||
'graph-bar' as const,
|
|
||||||
'heart' as const,
|
|
||||||
'heart-break' as const,
|
|
||||||
'history' as const,
|
|
||||||
'home' as const,
|
|
||||||
'home-alt' as const,
|
|
||||||
'horizontal-align-center' as const,
|
|
||||||
'horizontal-align-left' as const,
|
|
||||||
'horizontal-align-right' as const,
|
|
||||||
'hourglass' as const,
|
|
||||||
'import' as const,
|
|
||||||
'info' as const,
|
|
||||||
'info-circle' as const,
|
|
||||||
'key-skeleton-alt' as const,
|
|
||||||
'keyboard' as const,
|
|
||||||
'layer-group' as const,
|
|
||||||
'library-panel' as const,
|
|
||||||
'line-alt' as const,
|
|
||||||
'link' as const,
|
|
||||||
'list-ui-alt' as const,
|
|
||||||
'list-ul' as const,
|
|
||||||
'lock' as const,
|
|
||||||
'map-marker' as const,
|
|
||||||
'message' as const,
|
|
||||||
'minus' as const,
|
|
||||||
'minus-circle' as const,
|
|
||||||
'mobile-android' as const,
|
|
||||||
'monitor' as const,
|
|
||||||
'palette' as const,
|
|
||||||
'panel-add' as const,
|
|
||||||
'pause' as const,
|
|
||||||
'pen' as const,
|
|
||||||
'percentage' as const,
|
|
||||||
'play' as const,
|
|
||||||
'plug' as const,
|
|
||||||
'plus' as const,
|
|
||||||
'plus-circle' as const,
|
|
||||||
'plus-square' as const,
|
|
||||||
'power' as const,
|
|
||||||
'presentation-play' as const,
|
|
||||||
'process' as const,
|
|
||||||
'question-circle' as const,
|
|
||||||
'record-audio' as const,
|
|
||||||
'repeat' as const,
|
|
||||||
'rocket' as const,
|
|
||||||
'ruler-combined' as const,
|
|
||||||
'save' as const,
|
|
||||||
'search' as const,
|
|
||||||
'search-minus' as const,
|
|
||||||
'search-plus' as const,
|
|
||||||
'share-alt' as const,
|
|
||||||
'shield' as const,
|
|
||||||
'shield-exclamation' as const,
|
|
||||||
'signal' as const,
|
|
||||||
'signin' as const,
|
|
||||||
'signout' as const,
|
|
||||||
'sitemap' as const,
|
|
||||||
'slack' as const,
|
|
||||||
'sliders-v-alt' as const,
|
|
||||||
'sort-amount-down' as const,
|
|
||||||
'sort-amount-up' as const,
|
|
||||||
'square-shape' as const,
|
|
||||||
'star' as const,
|
|
||||||
'step-backward' as const,
|
|
||||||
'stopwatch-slash' as const,
|
|
||||||
'sync' as const,
|
|
||||||
'table' as const,
|
|
||||||
'tag-alt' as const,
|
|
||||||
'text-fields' as const,
|
|
||||||
'times' as const,
|
|
||||||
'toggle-on' as const,
|
|
||||||
'trash-alt' as const,
|
|
||||||
'unlock' as const,
|
|
||||||
'upload' as const,
|
|
||||||
'user' as const,
|
|
||||||
'users-alt' as const,
|
|
||||||
'vertical-align-bottom' as const,
|
|
||||||
'vertical-align-center' as const,
|
|
||||||
'vertical-align-top' as const,
|
|
||||||
'wrap-text' as const,
|
|
||||||
'rss' as const,
|
|
||||||
'x' as const,
|
|
||||||
];
|
|
||||||
|
|
||||||
// function remains for backwards compatibility
|
// function remains for backwards compatibility
|
||||||
export const getAvailableIcons = () => availableIcons;
|
export const getAvailableIcons = () => Object.keys(availableIconsIndex);
|
||||||
|
|
||||||
type BrandIconNames = typeof avaibleBrandIcons;
|
|
||||||
|
|
||||||
export type IconName = ReturnType<typeof getAvailableIcons>[number] | BrandIconNames[number];
|
|
||||||
|
|
||||||
/** Get the icon for a given field type */
|
/** Get the icon for a given field type */
|
||||||
export function getFieldTypeIcon(field?: Field): IconName {
|
export function getFieldTypeIcon(field?: Field): IconName {
|
||||||
@ -225,18 +34,3 @@ export function getFieldTypeIcon(field?: Field): IconName {
|
|||||||
}
|
}
|
||||||
return 'question-circle';
|
return 'question-circle';
|
||||||
}
|
}
|
||||||
|
|
||||||
function isValidIconName(iconName: string): iconName is IconName {
|
|
||||||
const namedIcons: string[] = availableIcons;
|
|
||||||
const brandIcons: string[] = avaibleBrandIcons;
|
|
||||||
|
|
||||||
return namedIcons.includes(iconName) || brandIcons.includes(iconName);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function toIconName(iconName: string): IconName | undefined {
|
|
||||||
if (isValidIconName(iconName)) {
|
|
||||||
return iconName;
|
|
||||||
}
|
|
||||||
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
@ -65,7 +65,7 @@ export function getNotFoundNav(): NavModel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function getWarningNav(text: string, subTitle?: string): NavModel {
|
export function getWarningNav(text: string, subTitle?: string): NavModel {
|
||||||
const node = {
|
const node: NavModelItem = {
|
||||||
text,
|
text,
|
||||||
subTitle,
|
subTitle,
|
||||||
icon: 'exclamation-triangle',
|
icon: 'exclamation-triangle',
|
||||||
|
@ -2,7 +2,7 @@ import { css } from '@emotion/css';
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
import { GrafanaTheme2 } from '@grafana/data';
|
import { GrafanaTheme2 } from '@grafana/data';
|
||||||
import { toIconName, useStyles2 } from '@grafana/ui';
|
import { useStyles2 } from '@grafana/ui';
|
||||||
import { Page } from 'app/core/components/Page/Page';
|
import { Page } from 'app/core/components/Page/Page';
|
||||||
import { useNavModel } from 'app/core/hooks/useNavModel';
|
import { useNavModel } from 'app/core/hooks/useNavModel';
|
||||||
|
|
||||||
@ -28,7 +28,7 @@ export function NavLandingPage({ navId }: Props) {
|
|||||||
<NavLandingPageCard
|
<NavLandingPageCard
|
||||||
key={child.id}
|
key={child.id}
|
||||||
description={child.description}
|
description={child.description}
|
||||||
icon={child.icon ? toIconName(child.icon) : undefined}
|
icon={child.icon}
|
||||||
text={child.text}
|
text={child.text}
|
||||||
url={child.url ?? ''}
|
url={child.url ?? ''}
|
||||||
/>
|
/>
|
||||||
@ -46,7 +46,7 @@ export function NavLandingPage({ navId }: Props) {
|
|||||||
<NavLandingPageCard
|
<NavLandingPageCard
|
||||||
key={child.id}
|
key={child.id}
|
||||||
description={child.description}
|
description={child.description}
|
||||||
icon={child.icon ? toIconName(child.icon) : undefined}
|
icon={child.icon}
|
||||||
text={child.text}
|
text={child.text}
|
||||||
url={child.url ?? ''}
|
url={child.url ?? ''}
|
||||||
/>
|
/>
|
||||||
|
@ -6,7 +6,7 @@ import { useLocation } from 'react-router-dom';
|
|||||||
|
|
||||||
import { GrafanaTheme2, NavSection } from '@grafana/data';
|
import { GrafanaTheme2, NavSection } from '@grafana/data';
|
||||||
import { locationService } from '@grafana/runtime';
|
import { locationService } from '@grafana/runtime';
|
||||||
import { Dropdown, FilterInput, Icon, Tooltip, useStyles2, toIconName } from '@grafana/ui';
|
import { Dropdown, FilterInput, Icon, Tooltip, useStyles2 } from '@grafana/ui';
|
||||||
import { contextSrv } from 'app/core/core';
|
import { contextSrv } from 'app/core/core';
|
||||||
import { useSearchQuery } from 'app/features/search/hooks/useSearchQuery';
|
import { useSearchQuery } from 'app/features/search/hooks/useSearchQuery';
|
||||||
import { StoreState } from 'app/types';
|
import { StoreState } from 'app/types';
|
||||||
@ -46,7 +46,6 @@ export function TopSearchBar() {
|
|||||||
|
|
||||||
const profileNode = configItems.find((item) => item.id === 'profile');
|
const profileNode = configItems.find((item) => item.id === 'profile');
|
||||||
const signInNode = configItems.find((item) => item.id === 'signin');
|
const signInNode = configItems.find((item) => item.id === 'signin');
|
||||||
const signInIconName = signInNode?.icon && toIconName(signInNode.icon);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.container}>
|
<div className={styles.container}>
|
||||||
@ -78,7 +77,7 @@ export function TopSearchBar() {
|
|||||||
{signInNode && (
|
{signInNode && (
|
||||||
<Tooltip placement="bottom" content="Sign in">
|
<Tooltip placement="bottom" content="Sign in">
|
||||||
<a className={styles.actionItem} href={signInNode.url} target={signInNode.target}>
|
<a className={styles.actionItem} href={signInNode.url} target={signInNode.target}>
|
||||||
{signInIconName && <Icon name={signInIconName} size="lg" />}
|
{signInNode.icon && <Icon name={signInNode.icon} size="lg" />}
|
||||||
</a>
|
</a>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
)}
|
)}
|
||||||
|
@ -3,7 +3,7 @@ import { pickBy } from 'lodash';
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
import { GrafanaTheme, GrafanaTheme2, DEFAULT_SAML_NAME } from '@grafana/data';
|
import { GrafanaTheme, GrafanaTheme2, DEFAULT_SAML_NAME } from '@grafana/data';
|
||||||
import { Icon, IconName, LinkButton, toIconName, useStyles, useTheme2, VerticalGroup } from '@grafana/ui';
|
import { Icon, IconName, LinkButton, useStyles, useTheme2, VerticalGroup } from '@grafana/ui';
|
||||||
import config from 'app/core/config';
|
import config from 'app/core/config';
|
||||||
|
|
||||||
export interface LoginService {
|
export interface LoginService {
|
||||||
@ -63,13 +63,13 @@ const loginServices: () => LoginServices = () => {
|
|||||||
bgColor: '#2f2f2f',
|
bgColor: '#2f2f2f',
|
||||||
enabled: oauthEnabled && Boolean(config.oauth.okta),
|
enabled: oauthEnabled && Boolean(config.oauth.okta),
|
||||||
name: config.oauth?.okta?.name || 'Okta',
|
name: config.oauth?.okta?.name || 'Okta',
|
||||||
icon: (config.oauth?.okta?.icon && toIconName(config.oauth.okta.icon)) || 'okta',
|
icon: config.oauth?.okta?.icon ?? ('okta' as const),
|
||||||
},
|
},
|
||||||
oauth: {
|
oauth: {
|
||||||
bgColor: '#262628',
|
bgColor: '#262628',
|
||||||
enabled: oauthEnabled && Boolean(config.oauth.generic_oauth),
|
enabled: oauthEnabled && Boolean(config.oauth.generic_oauth),
|
||||||
name: config.oauth?.generic_oauth?.name || 'OAuth',
|
name: config.oauth?.generic_oauth?.name || 'OAuth',
|
||||||
icon: (config.oauth?.generic_oauth?.icon && toIconName(config.oauth?.generic_oauth?.icon)) || 'signin',
|
icon: config.oauth?.generic_oauth?.icon ?? ('signin' as const),
|
||||||
hrefName: 'generic_oauth',
|
hrefName: 'generic_oauth',
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
@ -57,14 +57,13 @@ const Navigation = ({ children }: { children: NavModelItem[] }) => {
|
|||||||
<SelectNav customCss="page-header__select-nav">{children}</SelectNav>
|
<SelectNav customCss="page-header__select-nav">{children}</SelectNav>
|
||||||
<TabsBar className="page-header__tabs" hideBorder={true}>
|
<TabsBar className="page-header__tabs" hideBorder={true}>
|
||||||
{children.map((child, index) => {
|
{children.map((child, index) => {
|
||||||
const icon = child.icon ? toIconName(child.icon) : undefined;
|
|
||||||
return (
|
return (
|
||||||
!child.hideFromTabs && (
|
!child.hideFromTabs && (
|
||||||
<Tab
|
<Tab
|
||||||
label={child.text}
|
label={child.text}
|
||||||
active={child.active}
|
active={child.active}
|
||||||
key={`${child.url}-${index}`}
|
key={`${child.url}-${index}`}
|
||||||
icon={icon}
|
icon={child.icon}
|
||||||
href={child.url}
|
href={child.url}
|
||||||
suffix={child.tabSuffix}
|
suffix={child.tabSuffix}
|
||||||
/>
|
/>
|
||||||
|
@ -29,7 +29,7 @@ function buildWarningNav(text: string, subTitle?: string): NavModel {
|
|||||||
const node = {
|
const node = {
|
||||||
text,
|
text,
|
||||||
subTitle,
|
subTitle,
|
||||||
icon: 'exclamation-triangle',
|
icon: 'exclamation-triangle' as const,
|
||||||
};
|
};
|
||||||
return {
|
return {
|
||||||
node: node,
|
node: node,
|
||||||
|
@ -1458,7 +1458,7 @@ export const navIndex: NavIndex = {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
text: 'Configuration',
|
text: 'Configuration',
|
||||||
icon: 'fa fa-cog',
|
icon: 'cog',
|
||||||
url: '/plugins/basic-app',
|
url: '/plugins/basic-app',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
@ -1454,7 +1454,7 @@ export const navIndex: NavIndex = {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
text: 'Configuration',
|
text: 'Configuration',
|
||||||
icon: 'fa fa-cog',
|
icon: 'cog',
|
||||||
url: '/plugins/basic-app',
|
url: '/plugins/basic-app',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
@ -20,7 +20,7 @@ export function NewDataSourcePage() {
|
|||||||
|
|
||||||
export function getNavModel(): NavModel {
|
export function getNavModel(): NavModel {
|
||||||
const main = {
|
const main = {
|
||||||
icon: 'database',
|
icon: 'database' as const,
|
||||||
id: 'datasource-new',
|
id: 'datasource-new',
|
||||||
text: 'Add data source',
|
text: 'Add data source',
|
||||||
href: DATASOURCES_ROUTES.New,
|
href: DATASOURCES_ROUTES.New,
|
||||||
|
@ -3,7 +3,7 @@ import { contextSrv } from 'app/core/services/context_srv';
|
|||||||
import { AccessControlAction, FolderDTO } from 'app/types';
|
import { AccessControlAction, FolderDTO } from 'app/types';
|
||||||
|
|
||||||
export function buildNavModel(folder: FolderDTO): NavModelItem {
|
export function buildNavModel(folder: FolderDTO): NavModelItem {
|
||||||
const model = {
|
const model: NavModelItem = {
|
||||||
icon: 'folder',
|
icon: 'folder',
|
||||||
id: 'manage-folder',
|
id: 'manage-folder',
|
||||||
subTitle: 'Manage folder dashboards and permissions',
|
subTitle: 'Manage folder dashboards and permissions',
|
||||||
@ -21,7 +21,7 @@ export function buildNavModel(folder: FolderDTO): NavModelItem {
|
|||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
model.children.push({
|
model.children!.push({
|
||||||
active: false,
|
active: false,
|
||||||
icon: 'library-panel',
|
icon: 'library-panel',
|
||||||
id: `folder-library-panels-${folder.uid}`,
|
id: `folder-library-panels-${folder.uid}`,
|
||||||
@ -30,7 +30,7 @@ export function buildNavModel(folder: FolderDTO): NavModelItem {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (contextSrv.hasPermission(AccessControlAction.AlertingRuleRead)) {
|
if (contextSrv.hasPermission(AccessControlAction.AlertingRuleRead)) {
|
||||||
model.children.push({
|
model.children!.push({
|
||||||
active: false,
|
active: false,
|
||||||
icon: 'bell',
|
icon: 'bell',
|
||||||
id: `folder-alerting-${folder.uid}`,
|
id: `folder-alerting-${folder.uid}`,
|
||||||
@ -40,7 +40,7 @@ export function buildNavModel(folder: FolderDTO): NavModelItem {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (folder.canAdmin) {
|
if (folder.canAdmin) {
|
||||||
model.children.push({
|
model.children!.push({
|
||||||
active: false,
|
active: false,
|
||||||
icon: 'lock',
|
icon: 'lock',
|
||||||
id: `folder-permissions-${folder.uid}`,
|
id: `folder-permissions-${folder.uid}`,
|
||||||
@ -50,7 +50,7 @@ export function buildNavModel(folder: FolderDTO): NavModelItem {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (folder.canSave) {
|
if (folder.canSave) {
|
||||||
model.children.push({
|
model.children!.push({
|
||||||
active: false,
|
active: false,
|
||||||
icon: 'cog',
|
icon: 'cog',
|
||||||
id: `folder-settings-${folder.uid}`,
|
id: `folder-settings-${folder.uid}`,
|
||||||
|
@ -17,7 +17,7 @@ interface FormModel {
|
|||||||
|
|
||||||
const navModel = {
|
const navModel = {
|
||||||
main: {
|
main: {
|
||||||
icon: 'grafana',
|
icon: 'grafana' as const,
|
||||||
text: 'Invite',
|
text: 'Invite',
|
||||||
subTitle: 'Register your Grafana account',
|
subTitle: 'Register your Grafana account',
|
||||||
breadcrumbs: [{ title: 'Login', url: 'login' }],
|
breadcrumbs: [{ title: 'Login', url: 'login' }],
|
||||||
|
@ -11,7 +11,7 @@ import { getUserOrganizations, setUserOrganization } from './state/actions';
|
|||||||
|
|
||||||
const navModel = {
|
const navModel = {
|
||||||
main: {
|
main: {
|
||||||
icon: 'grafana',
|
icon: 'grafana' as const,
|
||||||
subTitle: 'Preferences',
|
subTitle: 'Preferences',
|
||||||
text: 'Select active organization',
|
text: 'Select active organization',
|
||||||
},
|
},
|
||||||
|
@ -13,7 +13,6 @@ export type Props = DataSourcePluginOptionsEditorProps<AlertManagerDataSourceJso
|
|||||||
const IMPL_OPTIONS: Array<SelectableValue<AlertManagerImplementation>> = [
|
const IMPL_OPTIONS: Array<SelectableValue<AlertManagerImplementation>> = [
|
||||||
{
|
{
|
||||||
value: AlertManagerImplementation.mimir,
|
value: AlertManagerImplementation.mimir,
|
||||||
icon: 'public/img/alerting/mimir_logo.svg',
|
|
||||||
label: 'Mimir',
|
label: 'Mimir',
|
||||||
description: `https://grafana.com/oss/mimir/. An open source, horizontally scalable, highly available, multi-tenant, long-term storage for Prometheus.`,
|
description: `https://grafana.com/oss/mimir/. An open source, horizontally scalable, highly available, multi-tenant, long-term storage for Prometheus.`,
|
||||||
},
|
},
|
||||||
|
@ -31,12 +31,11 @@ export function createNavModel(title: string, ...tabs: string[]): NavModel {
|
|||||||
breadcrumbs: [],
|
breadcrumbs: [],
|
||||||
};
|
};
|
||||||
|
|
||||||
const children = [];
|
const children: NavModelItem[] = [];
|
||||||
|
|
||||||
for (const tab of tabs) {
|
for (const tab of tabs) {
|
||||||
children.push({
|
children.push({
|
||||||
id: tab,
|
id: tab,
|
||||||
icon: 'icon',
|
|
||||||
subTitle: 'subTitle',
|
subTitle: 'subTitle',
|
||||||
url: title,
|
url: title,
|
||||||
text: title,
|
text: title,
|
||||||
|
Loading…
Reference in New Issue
Block a user