1
0
Fork 0
mirror of https://github.com/portainer/portainer.git synced 2025-08-05 05:45:22 +02:00

refactor(icons): replace fa icons [EE-4459] (#7907)

refactor(icons): remove fontawesome EE-4459

refactor(icon) replace feather with lucide EE-4472
This commit is contained in:
Ali 2022-11-28 15:00:28 +13:00 committed by GitHub
parent 9dfac98a26
commit d78b762f7b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
498 changed files with 2102 additions and 2817 deletions

View file

@ -1,6 +1,7 @@
import { Package } from 'react-feather';
import { Package } from 'lucide-react';
import { useEnvironmentId } from '@/react/hooks/useEnvironmentId';
import Subscription from '@/assets/ico/subscription.svg?c';
import { PageHeader } from '@@/PageHeader';
import { DashboardItem } from '@@/DashboardItem';
@ -9,8 +10,6 @@ import { DashboardGrid } from '@@/DashboardItem/DashboardGrid';
import { useResourceGroups } from '../queries/useResourceGroups';
import { useSubscriptions } from '../queries/useSubscriptions';
import SubscriptionsIcon from './icon-subscription.svg?c';
export function DashboardView() {
const environmentId = useEnvironmentId();
@ -35,7 +34,7 @@ export function DashboardView() {
<DashboardGrid>
<DashboardItem
value={subscriptionsCount as number}
icon={SubscriptionsIcon}
icon={Subscription}
type="Subscription"
/>
{!resourceGroupsQuery.isError && !resourceGroupsQuery.isLoading && (

View file

@ -1,3 +0,0 @@
<svg width="22" height="20" viewBox="0 0 22 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M20.8641 8.08333H1.69743M10.3224 16.7083L17.7974 16.7083C18.8709 16.7083 19.4076 16.7083 19.8176 16.4994C20.1782 16.3157 20.4714 16.0225 20.6552 15.6618C20.8641 15.2518 20.8641 14.7151 20.8641 13.6417V6.35833C20.8641 5.2849 20.8641 4.74818 20.6552 4.33819C20.4714 3.97754 20.1782 3.68433 19.8176 3.50057C19.4076 3.29167 18.8709 3.29167 17.7974 3.29167H16.0724M10.3224 16.7083L12.2391 18.625M10.3224 16.7083L12.2391 14.7917M6.48909 16.7083H4.76409C3.69066 16.7083 3.15394 16.7083 2.74394 16.4994C2.3833 16.3157 2.09009 16.0225 1.90633 15.6618C1.69743 15.2518 1.69743 14.7151 1.69743 13.6417V6.35833C1.69743 5.2849 1.69743 4.74818 1.90633 4.33818C2.09009 3.97754 2.3833 3.68433 2.74394 3.50057C3.15394 3.29167 3.69066 3.29167 4.76409 3.29167H12.2391M12.2391 3.29167L10.3224 5.20833M12.2391 3.29167L10.3224 1.375" stroke="#344054" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

Before

Width:  |  Height:  |  Size: 1,008 B

View file

@ -1,5 +1,6 @@
import { Field, Form, Formik } from 'formik';
import { useRouter } from '@uirouter/react';
import { Plus } from 'lucide-react';
import { ContainerInstanceFormValues } from '@/react/azure/types';
import * as notifications from '@/portainer/services/notifications';
@ -194,8 +195,8 @@ export function CreateContainerInstanceForm() {
disabled={!isValid}
isLoading={isSubmitting}
loadingText="Deployment in progress..."
icon={Plus}
>
<i className="fa fa-plus space-right" aria-hidden="true" />
Deploy the container
</LoadingButton>
</div>

View file

@ -1,10 +1,12 @@
import { FormikErrors } from 'formik';
import { ArrowRight } from 'lucide-react';
import { ButtonSelector } from '@@/form-components/ButtonSelector/ButtonSelector';
import { FormError } from '@@/form-components/FormError';
import { InputGroup } from '@@/form-components/InputGroup';
import { InputList } from '@@/form-components/InputList';
import { ItemProps } from '@@/form-components/InputList/InputList';
import { Icon } from '@@/Icon';
import styles from './PortsMappingField.module.css';
@ -82,7 +84,7 @@ function Item({
</InputGroup>
<span className="mx-3">
<i className="fa fa-long-arrow-alt-right" aria-hidden="true" />
<Icon icon={ArrowRight} />
</span>
<InputGroup size="small">

View file

@ -1,4 +1,4 @@
import { Box, Plus, Trash2 } from 'react-feather';
import { Box, Plus, Trash2 } from 'lucide-react';
import { useStore } from 'zustand';
import { ContainerGroup } from '@/react/azure/types';

View file

@ -1,8 +1,11 @@
import { CellProps, Column } from 'react-table';
import { ExternalLink } from 'lucide-react';
import { ContainerGroup } from '@/react/azure/types';
import { getPorts } from '@/react/azure/utils';
import { Icon } from '@@/Icon';
export const ports: Column<ContainerGroup> = {
Header: 'Published Ports',
accessor: (container) => getPorts(container),
@ -26,8 +29,8 @@ function PortsCell({
return ports.map((port) => (
<a className="image-tag" href={`http://${ip}:${port.host}`} key={port.host}>
<i className="fa fa-external-link-alt" aria-hidden="true" /> {ip}:
{port.host}
<Icon icon={ExternalLink} className="mr-1" />
{ip}:{port.host}
</a>
));
}

View file

@ -1,11 +1,13 @@
import { PropsWithChildren } from 'react';
import clsx from 'clsx';
import { Briefcase } from 'react-feather';
import { Briefcase } from 'lucide-react';
import './BEFeatureIndicator.css';
import { FeatureId } from '@/react/portainer/feature-flags/enums';
import { Icon } from '@@/Icon';
import { getFeatureDetails } from './utils';
export interface Props {
@ -33,8 +35,8 @@ export function BEFeatureIndicator({
rel="noopener noreferrer"
>
{children}
{showIcon && <Briefcase className="icon icon-sm vertical-center" />}
<span className="be-indicator-label break-words space-left">
{showIcon && <Icon icon={Briefcase} className="mr-1 be-indicator-icon" />}
<span className="be-indicator-label break-words">
Business Edition Feature
</span>
</a>

View file

@ -29,7 +29,7 @@ function Template({
size?: BadgeSize;
icon: string;
}) {
return <BadgeIcon icon={icon} size={size} featherIcon />;
return <BadgeIcon icon={icon} size={size} />;
}
export const Example = Template.bind({});

View file

@ -8,7 +8,7 @@ export interface Props extends IconProps {
size?: BadgeSize;
}
export function BadgeIcon({ icon, featherIcon, size = '3xl' }: Props) {
export function BadgeIcon({ icon, size = '3xl' }: Props) {
const sizeClasses = iconSizeToClasses(size);
return (
<div
@ -22,7 +22,7 @@ export function BadgeIcon({ icon, featherIcon, size = '3xl' }: Props) {
`
)}
>
<Icon icon={icon} feather={featherIcon} className="feather !flex" />
<Icon icon={icon} className="!flex" />
</div>
);
}

View file

@ -1,5 +1,6 @@
import { Meta } from '@storybook/react';
import { useState } from 'react';
import { User } from 'lucide-react';
import { init as initFeatureService } from '@/react/portainer/feature-flags/feature-flags.service';
import { Edition, FeatureId } from '@/react/portainer/feature-flags/enums';
@ -21,14 +22,14 @@ function Example() {
const options: BoxSelectorOption<number>[] = [
{
description: 'description 1',
icon: 'fa fa-rocket',
icon: User,
id: '1',
value: 3,
label: 'option 1',
},
{
description: 'description 2',
icon: 'fa fa-rocket',
icon: User,
id: '2',
value: 4,
label: 'option 2',
@ -53,14 +54,14 @@ function LimitedFeature() {
const options: BoxSelectorOption<number>[] = [
{
description: 'description 1',
icon: 'fa fa-rocket',
icon: User,
id: '1',
value: 3,
label: 'option 1',
},
{
description: 'description 2',
icon: 'fa fa-rocket',
icon: User,
id: '2',
value: 4,
label: 'option 2',

View file

@ -1,3 +1,5 @@
import { Rocket } from 'lucide-react';
import { render, fireEvent } from '@/react-tools/test-utils';
import { BoxSelector, Props } from './BoxSelector';
@ -23,14 +25,14 @@ test('should render with the initial value selected and call onChange when click
const options: BoxSelectorOption<number>[] = [
{
description: 'description 1',
icon: 'fa fa-rocket',
icon: Rocket,
id: '1',
value: 3,
label: 'option 1',
},
{
description: 'description 2',
icon: 'fa fa-rocket',
icon: Rocket,
id: '2',
value: 4,
label: 'option 2',

View file

@ -13,11 +13,6 @@
color: var(--text-boxselector-header);
}
.boxselector_header .fa,
.fab {
font-weight: normal;
}
.boxselector_wrapper input[type='radio'],
.box-selector-item input[type='radio'] {
display: none;

View file

@ -1,8 +1,11 @@
import { Meta } from '@storybook/react';
import { User } from 'lucide-react';
import { init as initFeatureService } from '@/react/portainer/feature-flags/feature-flags.service';
import { Edition, FeatureId } from '@/react/portainer/feature-flags/enums';
import { IconProps } from '@@/Icon';
import { BoxSelectorItem } from './BoxSelectorItem';
import { BoxSelectorOption } from './types';
@ -11,7 +14,7 @@ const meta: Meta = {
args: {
selected: false,
description: 'description',
icon: 'fa-rocket',
icon: User,
label: 'label',
},
};
@ -21,7 +24,7 @@ export default meta;
interface ExampleProps {
selected?: boolean;
description?: string;
icon?: string;
icon?: IconProps['icon'];
label?: string;
feature?: FeatureId;
}
@ -35,7 +38,7 @@ function Template({
}: ExampleProps) {
const option: BoxSelectorOption<number> = {
description,
icon: `fa ${icon}`,
icon,
id: 'id',
label,
value: 1,

View file

@ -54,7 +54,6 @@ export function BoxSelectorItem<T extends number | string>({
{!!option.icon && (
<Icon
icon={option.icon}
feather={option.featherIcon}
className="boxselector_icon !flex items-center"
/>
)}

View file

@ -1,5 +1,5 @@
import ReactTooltip from 'react-tooltip';
import { HelpCircle } from 'react-feather';
import { HelpCircle } from 'lucide-react';
import { FeatureId } from '@/react/portainer/feature-flags/enums';
@ -21,7 +21,7 @@ export function LimitedToBeIndicator({ tooltipId, featureId }: Props) {
<span className="text-warning-9">Pro Feature</span>
</a>
<HelpCircle
className="feather !text-warning-7"
className="lucide !text-warning-7"
data-tip
data-for={tooltipId}
tooltip-append-to-body="true"

View file

@ -2,15 +2,15 @@ import { Icon, IconProps } from '@@/Icon';
type Props = IconProps;
export function LogoIcon({ icon, featherIcon }: Props) {
export function LogoIcon({ icon }: Props) {
return (
<div
className={`
text-6xl h-14 w-14
inline-flex items-center justify-center
inline-flex items-center justify-center
`}
>
<Icon icon={icon} feather={featherIcon} className="feather !flex" />
<Icon icon={icon} className="!flex" />
</div>
);
}

View file

@ -1,4 +1,4 @@
import { Edit, FileText, Globe, Upload } from 'react-feather';
import { Edit, FileText, Globe, Upload } from 'lucide-react';
import GitIcon from '@/assets/ico/git.svg?c';

View file

@ -1,7 +1,8 @@
import clsx from 'clsx';
import { Check, Copy } from 'lucide-react';
import { Button } from '@@/buttons';
import { useCopy } from '@@/buttons/CopyButton/useCopy';
import { Icon } from '@@/Icon';
import styles from './Code.module.css';
@ -19,12 +20,10 @@ export function Code({ children, showCopyButton }: Props) {
{showCopyButton && (
<Button color="link" className={styles.copyButton} onClick={handleCopy}>
<i
className={clsx(
'fa',
copiedSuccessfully ? 'fa-check green-icon' : 'fa-copy '
)}
aria-hidden="true"
<Icon
icon={copiedSuccessfully ? Check : Copy}
className="!ml-1"
mode={copiedSuccessfully ? 'success' : undefined}
/>
</Button>
)}

View file

@ -1,6 +1,8 @@
import { Meta, Story } from '@storybook/react';
import { List } from 'lucide-react';
import { Link } from '@@/Link';
import { IconProps } from '@@/Icon';
import { DashboardItem } from './DashboardItem';
@ -12,7 +14,7 @@ export default meta;
interface StoryProps {
value: number;
icon: string;
icon: IconProps['icon'];
type: string;
}
@ -23,21 +25,21 @@ function Template({ value, icon, type }: StoryProps) {
export const Primary: Story<StoryProps> = Template.bind({});
Primary.args = {
value: 1,
icon: 'fa fa-th-list',
icon: List,
type: 'Example resource',
};
export function WithLink() {
return (
<Link to="example.page">
<DashboardItem value={1} icon="fa fa-th-list" type="Example resource" />
<DashboardItem value={1} icon={List} type="Example resource" />
</Link>
);
}
export function WithChildren() {
return (
<DashboardItem value={1} icon="fa fa-th-list" type="Example resource">
<DashboardItem value={1} icon={List} type="Example resource">
<div>Children</div>
</DashboardItem>
);

View file

@ -1,3 +1,5 @@
import { User } from 'lucide-react';
import { render } from '@/react-tools/test-utils';
import { DashboardItem } from './DashboardItem';
@ -11,7 +13,7 @@ test('should show provided resource value', async () => {
});
test('should show provided resource type', async () => {
const { getByLabelText } = renderComponent(0, '', 'Test');
const { getByLabelText } = renderComponent(0, User, 'Test');
const title = getByLabelText('resourceType');
expect(title).toBeVisible();
@ -19,11 +21,11 @@ test('should show provided resource type', async () => {
});
test('should have accessibility label created from the provided resource type', async () => {
const { getByLabelText } = renderComponent(0, '', 'testLabel');
const { getByLabelText } = renderComponent(0, User, 'testLabel');
expect(getByLabelText('testLabel')).toBeTruthy();
});
function renderComponent(value = 0, icon = '', type = '') {
function renderComponent(value = 0, icon = User, type = '') {
return render(<DashboardItem value={value} icon={icon} type={type} />);
}

View file

@ -10,13 +10,7 @@ interface Props extends IconProps {
children?: ReactNode;
}
export function DashboardItem({
value,
icon,
type,
children,
featherIcon,
}: Props) {
export function DashboardItem({ value, icon, type, children }: Props) {
return (
<div
className={clsx(
@ -35,7 +29,7 @@ export function DashboardItem({
'th-highcontrast:bg-blue-3 th-highcontrast:text-blue-8'
)}
>
<Icon icon={icon} feather={featherIcon} className="feather" />
<Icon icon={icon} />
</div>
<div className="flex flex-col justify-around">

View file

@ -9,8 +9,6 @@ interface Props {
alt?: string;
size?: BadgeSize;
className?: string;
// additional fallback badge props
feather?: boolean;
}
export function FallbackImage({
@ -19,7 +17,6 @@ export function FallbackImage({
alt,
size,
className,
feather,
}: Props) {
const [error, setError] = useState(false);
@ -39,5 +36,5 @@ export function FallbackImage({
}
// fallback icon if there is an error loading the image
return <BadgeIcon icon={fallbackIcon} featherIcon={feather} size={size} />;
return <BadgeIcon icon={fallbackIcon} size={size} />;
}

View file

@ -1,13 +1,12 @@
import clsx from 'clsx';
import { ComponentType, ReactNode } from 'react';
import * as featherIcons from 'react-feather';
import * as lucideIcons from 'lucide-react';
import { isValidElementType } from 'react-is';
import Svg, { SvgIcons } from './Svg';
export interface IconProps {
icon: ReactNode | ComponentType<unknown>;
featherIcon?: boolean;
}
export type IconMode =
@ -27,16 +26,15 @@ export type IconSize = 'xs' | 'sm' | 'md' | 'lg' | 'xl';
interface Props {
icon: ReactNode | ComponentType<{ size?: string | number }>;
feather?: boolean;
className?: string;
size?: IconSize;
mode?: IconMode;
}
export function Icon({ icon, feather, className, mode, size }: Props) {
export function Icon({ icon, className, mode, size }: Props) {
const classes = clsx(
className,
'icon',
'icon inline-flex',
{ [`icon-${mode}`]: mode },
{ [`icon-${size}`]: size }
);
@ -44,9 +42,13 @@ export function Icon({ icon, feather, className, mode, size }: Props) {
if (typeof icon !== 'string') {
const Icon = isValidElementType(icon) ? icon : null;
if (Icon) {
return <Icon className={classes} aria-hidden="true" role="img" />;
}
return (
<span className={classes} aria-hidden="true" role="img">
{Icon == null ? <>{icon}</> : <Icon size="1em" />}
{icon}
</span>
);
}
@ -56,28 +58,25 @@ export function Icon({ icon, feather, className, mode, size }: Props) {
return (
<Svg
icon={svgIcon as keyof typeof SvgIcons}
className={clsx(classes, '!flex')}
className={classes}
aria-hidden="true"
/>
);
}
if (feather) {
const iconName = icon
.split('-')
.map((s) => s.slice(0, 1).toUpperCase() + s.slice(1))
.join('') as keyof typeof featherIcons;
const IconComponent = featherIcons[iconName];
if (!IconComponent) {
throw new Error(`Feather icon not found: ${iconName}`);
}
return <IconComponent className={classes} />;
const iconName = icon
.split('-')
.map((s) => s.slice(0, 1).toUpperCase() + s.slice(1))
.join('') as keyof typeof lucideIcons;
const IconComponent = lucideIcons[iconName] as React.FC<
React.SVGProps<SVGSVGElement>
>;
if (!IconComponent) {
// console error so that the error is logged but no functionality is broken
// eslint-disable-next-line no-console
console.error(`Icon not found: '${icon}'`);
return null;
}
return (
<i
className={clsx(icon.startsWith('fa-') ? `fa ${icon}` : icon, classes)}
aria-hidden="true"
role="img"
/>
);
return <IconComponent className={classes} aria-hidden="true" />;
}

View file

@ -1,4 +1,5 @@
import { PropsWithChildren } from 'react';
import { X } from 'lucide-react';
import { Widget, WidgetBody } from './Widget';
import { Button } from './buttons';
@ -32,8 +33,8 @@ export function InformationPanel({
style={{ float: 'right' }}
ng-if="dismissAction"
>
<Button color="link" onClick={() => onDismiss()}>
<i className="fa fa-times" /> dismiss
<Button color="link" icon={X} onClick={() => onDismiss()}>
dismiss
</Button>
</span>
)}

View file

@ -4,7 +4,7 @@ import { ReactNode } from 'react';
import styles from './NavTabs.module.css';
export interface Option<T extends string | number = string> {
label: string | ReactNode;
label: ReactNode;
children?: ReactNode;
id: T;
}

View file

@ -1,4 +1,4 @@
import { HelpCircle } from 'react-feather';
import { HelpCircle } from 'lucide-react';
import clsx from 'clsx';
import { getDocURL } from '@@/PageHeader/ContextHelp/docURLs';
@ -24,7 +24,7 @@ export function ContextHelp() {
)}
title="Help"
>
<HelpCircle className="feather" onClick={onHelpClick} />
<HelpCircle className="lucide" onClick={onHelpClick} />
</div>
</div>
);

View file

@ -9,6 +9,7 @@ import { UISrefProps, useSref } from '@uirouter/react';
import Moment from 'moment';
import { useEffect, useState } from 'react';
import { useStore } from 'zustand';
import { AlertCircle, Bell, CheckCircle, Trash2 } from 'lucide-react';
import { AutomationTestingProps } from '@/types';
import { useUser } from '@/react/hooks/useUser';
@ -69,7 +70,7 @@ export function NotificationsMenu() {
'th-dark:text-gray-warm-7'
)}
>
<Icon icon="bell" feather />
<Icon icon={Bell} />
<span className={badge ? notificationStyles.badge : ''} />
</div>
</MenuButton>
@ -126,7 +127,7 @@ export function NotificationsMenu() {
</>
) : (
<div className="flex flex-col items-center">
<Icon icon="bell" feather size="xl" />
<Icon icon={Bell} size="xl" />
<p className="my-5">You have no notifications yet.</p>
</div>
)}
@ -160,9 +161,9 @@ function MenuLink({ to, params, notification, onDelete }: MenuLinkProps) {
<div className={notificationStyles.container}>
<div className={notificationStyles.notificationIcon}>
{notification.type === 'success' ? (
<Icon icon="check-circle" feather size="lg" mode="success" />
<Icon icon={CheckCircle} size="lg" mode="success" />
) : (
<Icon icon="alert-circle" feather size="lg" mode="danger" />
<Icon icon={AlertCircle} size="lg" mode="danger" />
)}
</div>
<div className={notificationStyles.notificationBody}>
@ -186,9 +187,8 @@ function MenuLink({ to, params, notification, onDelete }: MenuLinkProps) {
}}
data-cy="notification-deleteButton"
size="large"
>
<Icon icon="trash-2" feather />
</Button>
icon={Trash2}
/>
</div>
</div>
</ReachMenuLink>

View file

@ -1,5 +1,5 @@
import { useRouter } from '@uirouter/react';
import { RefreshCw } from 'react-feather';
import { RefreshCw } from 'lucide-react';
import { Button } from '../buttons';

View file

@ -6,7 +6,7 @@ import {
} from '@reach/menu-button';
import { UISrefProps, useSref } from '@uirouter/react';
import clsx from 'clsx';
import { User, ChevronDown } from 'react-feather';
import { User, ChevronDown } from 'lucide-react';
import { AutomationTestingProps } from '@/types';
import { useUser } from '@/react/hooks/useUser';
@ -34,7 +34,7 @@ export function UserMenu() {
'th-dark:text-gray-warm-7'
)}
>
<User className="feather" />
<User className="lucide" />
</div>
{user && <span>{user.Username}</span>}
<ChevronDown className={styles.arrowDown} />

View file

@ -1,3 +1,5 @@
import { AlertTriangle, Check } from 'lucide-react';
import { usePublicSettings } from '@/react/portainer/settings/queries';
import { Icon } from '@@/Icon';
@ -17,12 +19,12 @@ export function PasswordCheckHint({
return (
<div>
<p className="text-warning vertical-center">
<Icon icon="alert-triangle" className="icon-warning" feather />
<Icon icon={AlertTriangle} className="icon-warning" />
{forceChangePassword &&
'An administrator has changed your password requirements, '}
The password must be at least {minPasswordLength} characters long.
{passwordValid && (
<i className="fa fa-check green-icon space-left" aria-hidden="true" />
<Icon icon={Check} className="!ml-1" mode="success" />
)}
</p>
</div>

View file

@ -3,57 +3,19 @@ import automode from '@/assets/ico/theme/auto.svg?c';
import darkmode from '@/assets/ico/theme/darkmode.svg?c';
import lightmode from '@/assets/ico/theme/lightmode.svg?c';
import highcontrastmode from '@/assets/ico/theme/highcontrastmode.svg?c';
// wizard icons
import agent from '@/assets/ico/wizard/agent.svg?c';
import api from '@/assets/ico/wizard/api.svg?c';
import edgeagent from '@/assets/ico/wizard/edge-agent.svg?c';
import cloudimport from '@/assets/ico/wizard/import.svg?c';
import socket from '@/assets/ico/wizard/socket.svg?c';
// general icons
import arrowsupdown from '@/assets/ico/arrows-updown.svg?c';
import arrowright from '@/assets/ico/arrow-right-long.svg?c';
import bomb from '@/assets/ico/bomb.svg?c';
import checked from '@/assets/ico/checked.svg?c';
import circlenotch from '@/assets/ico/circle-notch.svg?c';
import clockrewind from '@/assets/ico/clock-rewind.svg?c';
import compress from '@/assets/ico/compress.svg?c';
import cubes from '@/assets/ico/cubes.svg?c';
import custom from '@/assets/ico/custom.svg?c';
import dataflow from '@/assets/ico/dataflow-1.svg?c';
import dataflow2 from '@/assets/ico/dataflow-2.svg?c';
import expand from '@/assets/ico/expand.svg?c';
import filecode from '@/assets/ico/file-code.svg?c';
import filesignature from '@/assets/ico/file-signature.svg?c';
import fileupload from '@/assets/ico/file-upload.svg?c';
import flask from '@/assets/ico/flask.svg?c';
import git from '@/assets/ico/git.svg?c';
import hacker from '@/assets/ico/hacker.svg?c';
import heartbeat from '@/assets/ico/heartbeat.svg?c';
import laptop from '@/assets/ico/laptop.svg?c';
import kube from '@/assets/ico/kube.svg?c';
import laptopcode from '@/assets/ico/laptop-code.svg?c';
import ldap from '@/assets/ico/ldap.svg?c';
import magic from '@/assets/ico/magic.svg?c';
import magicwand from '@/assets/ico/magic-wand.svg?c';
import linux from '@/assets/ico/linux.svg?c';
import memory from '@/assets/ico/memory.svg?c';
import objectgroup from '@/assets/ico/object-group.svg?c';
import palette from '@/assets/ico/palette.svg?c';
import plug from '@/assets/ico/plug.svg?c';
import restore from '@/assets/ico/restore.svg?c';
import restorewindow from '@/assets/ico/restore-window.svg?c';
import rocket from '@/assets/ico/rocket.svg?c';
import route from '@/assets/ico/route.svg?c';
import share from '@/assets/ico/share.svg?c';
import sort from '@/assets/ico/sort.svg?c';
import tachometer from '@/assets/ico/tachometer.svg?c';
import template from '@/assets/ico/template.svg?c';
import tag from '@/assets/ico/tag-2.svg?c';
import tag2 from '@/assets/ico/tags.svg?c';
import tools from '@/assets/ico/tools.svg?c';
import upload from '@/assets/ico/upload.svg?c';
import url from '@/assets/ico/url.svg?c';
import usercircle from '@/assets/ico/user-circle.svg?c';
import userlock from '@/assets/ico/user-lock.svg?c';
import kube from '@/assets/ico/kube.svg?c';
import subscription from '@/assets/ico/subscription.svg?c';
import Placeholder from '@/assets/ico/placeholder.svg?c'; // Placeholder is used when an icon name cant be matched
// vendor icons
import aws from '@/assets/ico/vendor/aws.svg?c';
@ -61,6 +23,7 @@ import azure from '@/assets/ico/vendor/azure.svg?c';
import civo from '@/assets/ico/vendor/civo.svg?c';
import digitalocean from '@/assets/ico/vendor/digitalocean.svg?c';
import docker from '@/assets/ico/vendor/docker.svg?c';
import dockericon from '@/assets/ico/vendor/docker-icon.svg?c';
import dockercompose from '@/assets/ico/vendor/docker-compose.svg?c';
import ecr from '@/assets/ico/vendor/ecr.svg?c';
import github from '@/assets/ico/vendor/github.svg?c';
@ -71,68 +34,33 @@ import kubernetes from '@/assets/ico/vendor/kubernetes.svg?c';
import helm from '@/assets/ico/vendor/helm.svg?c';
import linode from '@/assets/ico/vendor/linode.svg?c';
import microsoft from '@/assets/ico/vendor/microsoft.svg?c';
import microsofticon from '@/assets/ico/vendor/microsoft-icon.svg?c';
import nomad from '@/assets/ico/vendor/nomad.svg?c';
import nomadicon from '@/assets/ico/vendor/nomad-icon.svg?c';
import openldap from '@/assets/ico/vendor/openldap.svg?c';
import proget from '@/assets/ico/vendor/proget.svg?c';
import quay from '@/assets/ico/vendor/quay.svg?c';
import internal from '@/assets/ico/vendor/internal.svg?c';
const placeholder = Placeholder;
export const SvgIcons = {
agent,
api,
edgeagent,
cloudimport,
socket,
automode,
darkmode,
lightmode,
highcontrastmode,
dataflow,
dataflow2,
arrowsupdown,
arrowright,
bomb,
checked,
circlenotch,
clockrewind,
compress,
cubes,
custom,
expand,
filecode,
filesignature,
fileupload,
flask,
dockericon,
git,
hacker,
heartbeat,
laptop,
laptopcode,
ldap,
magic,
magicwand,
linux,
memory,
objectgroup,
palette,
placeholder,
plug,
restore,
restorewindow,
rocket,
route,
share,
sort,
tachometer,
template,
tag,
tag2,
tools,
upload,
url,
usercircle,
userlock,
subscription,
aws,
azure,
civo,
@ -148,11 +76,12 @@ export const SvgIcons = {
helm,
linode,
microsoft,
microsofticon,
nomad,
nomadicon,
openldap,
proget,
quay,
internal,
kube,
};

View file

@ -1,5 +1,6 @@
import clsx from 'clsx';
import _ from 'lodash';
import { Trash2 } from 'lucide-react';
import { TagId } from '@/portainer/tags/types';
import { Icon } from '@/react/components/Icon';
@ -74,7 +75,7 @@ export function TagSelector({ value, allowCreate = false, onChange }: Props) {
key={tag.value}
>
{tag.label}
<Icon icon="trash-2" feather />
<Icon icon={Trash2} />
</button>
))}
</FormControl>

View file

@ -1,5 +1,6 @@
import clsx from 'clsx';
import { PropsWithChildren } from 'react';
import { AlertCircle } from 'lucide-react';
import { Icon } from '@@/Icon';
@ -30,8 +31,7 @@ export function TextTip({
<p className="small vertical-center">
<i className="icon-container">
<Icon
icon="alert-circle"
feather
icon={AlertCircle}
className={clsx(`${iconClass}`, 'space-right')}
/>
</i>

View file

@ -1,5 +1,5 @@
import ReactTooltip from 'react-tooltip';
import { HelpCircle } from 'react-feather';
import { HelpCircle } from 'lucide-react';
import clsx from 'clsx';
import _ from 'lodash';
@ -22,7 +22,7 @@ export function Tooltip({ message, position = 'bottom', className }: Props) {
data-for={id}
className={clsx(styles.icon, 'inline-flex text-base')}
>
<HelpCircle className="feather" aria-hidden="true" />
<HelpCircle className="lucide" aria-hidden="true" />
<ReactTooltip
id={id}
multiline

View file

@ -1,4 +1,7 @@
import clsx from 'clsx';
import { Settings } from 'lucide-react';
import { Icon } from '@@/Icon';
import styles from './ViewLoading.module.css';
@ -18,7 +21,7 @@ export function ViewLoading({ message }: Props) {
{message && (
<span className={styles.message}>
{message}
<i className="fa fa-cog fa-spin space-left" />
<Icon icon={Settings} className="animate-spin-slow !ml-1" />
</span>
)}
</div>

View file

@ -1,4 +1,6 @@
import { ReactNode } from 'react';
import type { Meta } from '@storybook/react';
import { User } from 'lucide-react';
import { Widget } from './Widget';
import { WidgetBody } from './WidgetBody';
@ -9,7 +11,7 @@ import { WidgetTaskbar } from './WidgetTaskbar';
interface WidgetProps {
loading: boolean;
title: string;
icon: string;
icon: ReactNode;
bodyText: string;
footerText: string;
}
@ -20,7 +22,7 @@ const meta: Meta<WidgetProps> = {
args: {
loading: false,
title: 'Title',
icon: 'fa-rocket',
icon: User,
bodyText: 'Body',
footerText: 'Footer',
},
@ -52,11 +54,15 @@ function WidgetWithCustomImage({
<WidgetTitle
title={title}
icon={
<img
className="custom-header-ico space-right"
src={icon}
alt="header-icon"
/>
typeof icon === 'string' ? (
<img
className="custom-header-ico space-right"
src={icon}
alt="header-icon"
/>
) : (
icon
)
}
/>
<WidgetBody loading={loading}>{bodyText}</WidgetBody>

View file

@ -8,7 +8,6 @@ import { useWidgetContext } from './Widget';
interface Props {
title: ReactNode;
icon: ReactNode;
featherIcon?: boolean;
className?: string;
}
@ -17,7 +16,6 @@ export function WidgetTitle({
icon,
className,
children,
featherIcon,
}: PropsWithChildren<Props>) {
useWidgetContext();
@ -26,11 +24,7 @@ export function WidgetTitle({
<div className="row">
<span className={clsx('pull-left vertical-center', className)}>
<div className="widget-icon">
<Icon
icon={icon}
feather={featherIcon}
className="space-right feather"
/>
<Icon icon={icon} className="space-right" />
</div>
<span>{title}</span>
</span>

View file

@ -1,4 +1,5 @@
import clsx from 'clsx';
import { PlusCircle } from 'lucide-react';
import { Icon } from '@/react/components/Icon';
@ -27,7 +28,7 @@ export function AddButton({ label, onClick, className, disabled }: Props) {
onClick={onClick}
disabled={disabled}
>
<Icon icon="plus-circle" feather />
<Icon icon={PlusCircle} />
{label}
</button>
);

View file

@ -1,6 +1,6 @@
import { Meta, Story } from '@storybook/react';
import { PropsWithChildren } from 'react';
import { Download } from 'react-feather';
import { Download } from 'lucide-react';
import { Button, Props } from './Button';

View file

@ -27,7 +27,6 @@ type Size = 'xsmall' | 'small' | 'medium' | 'large';
export interface Props extends AriaAttributes, AutomationTestingProps {
icon?: ReactNode | ComponentType<unknown>;
featherIcon?: boolean;
color?: Color;
size?: Size;
@ -47,7 +46,6 @@ export function Button({
onClick,
title,
icon,
featherIcon,
children,
...ariaProps
@ -64,12 +62,7 @@ export function Button({
{...ariaProps}
>
{icon && (
<Icon
icon={icon}
size={getIconSize(size)}
className="inline-flex"
feather={featherIcon}
/>
<Icon icon={icon} size={getIconSize(size)} className="inline-flex" />
)}
{children}
</button>

View file

@ -1,5 +1,6 @@
import { Meta, Story } from '@storybook/react';
import { PropsWithChildren } from 'react';
import { Play, RefreshCw, Square, Trash2 } from 'lucide-react';
import { Button } from './Button';
import { ButtonGroup, Props } from './ButtonGroup';
@ -14,28 +15,19 @@ function Template({
}: JSX.IntrinsicAttributes & PropsWithChildren<Props>) {
return (
<ButtonGroup size={size}>
<Button color="primary" onClick={() => {}}>
<i className="fa fa-play space-right" aria-hidden="true" />
<Button icon={Play} color="primary" onClick={() => {}}>
Start
</Button>
<Button color="danger" onClick={() => {}}>
<i className="fa fa-stop space-right" aria-hidden="true" />
<Button icon={Square} color="danger" onClick={() => {}}>
Stop
</Button>
<Button color="danger" onClick={() => {}}>
<i className="fa fa-bomb space-right" aria-hidden="true" />
Kill
</Button>
<Button color="primary" onClick={() => {}}>
<i className="fa fa-sync space-right" aria-hidden="true" />
<Button icon={RefreshCw} color="primary" onClick={() => {}}>
Restart
</Button>
<Button color="primary" disabled onClick={() => {}}>
<i className="fa fa-play space-right" aria-hidden="true" />
<Button icon={Play} color="primary" disabled onClick={() => {}}>
Resume
</Button>
<Button color="danger" onClick={() => {}}>
<i className="fa fa-trash-alt space-right" aria-hidden="true" />
<Button icon={Trash2} color="danger" onClick={() => {}}>
Remove
</Button>
</ButtonGroup>
@ -50,20 +42,16 @@ Primary.args = {
export function Xsmall() {
return (
<ButtonGroup size="xsmall">
<Button color="primary" onClick={() => {}}>
<i className="fa fa-play space-right" aria-hidden="true" />
<Button icon={Play} color="primary" onClick={() => {}}>
Start
</Button>
<Button color="danger" onClick={() => {}}>
<i className="fa fa-stop space-right" aria-hidden="true" />
<Button icon={Square} color="danger" onClick={() => {}}>
Stop
</Button>
<Button color="primary" onClick={() => {}}>
<i className="fa fa-play space-right" aria-hidden="true" />
<Button icon={Play} color="primary" onClick={() => {}}>
Start
</Button>
<Button color="primary" onClick={() => {}}>
<i className="fa fa-sync space-right" aria-hidden="true" />
<Button icon={RefreshCw} color="primary" onClick={() => {}}>
Restart
</Button>
</ButtonGroup>
@ -73,20 +61,16 @@ export function Xsmall() {
export function Small() {
return (
<ButtonGroup size="small">
<Button color="primary" onClick={() => {}}>
<i className="fa fa-play space-right" aria-hidden="true" />
<Button icon={Play} color="primary" onClick={() => {}}>
Start
</Button>
<Button color="danger" onClick={() => {}}>
<i className="fa fa-stop space-right" aria-hidden="true" />
<Button icon={Square} color="danger" onClick={() => {}}>
Stop
</Button>
<Button color="primary" onClick={() => {}}>
<i className="fa fa-play space-right" aria-hidden="true" />
<Button icon={Play} color="primary" onClick={() => {}}>
Start
</Button>
<Button color="primary" onClick={() => {}}>
<i className="fa fa-sync space-right" aria-hidden="true" />
<Button icon={RefreshCw} color="primary" onClick={() => {}}>
Restart
</Button>
</ButtonGroup>
@ -96,20 +80,16 @@ export function Small() {
export function Large() {
return (
<ButtonGroup size="large">
<Button color="primary" onClick={() => {}}>
<i className="fa fa-play space-right" aria-hidden="true" />
<Button icon={Play} color="primary" onClick={() => {}}>
Start
</Button>
<Button color="danger" onClick={() => {}}>
<i className="fa fa-stop space-right" aria-hidden="true" />
<Button icon={Square} color="danger" onClick={() => {}}>
Stop
</Button>
<Button color="light" onClick={() => {}}>
<i className="fa fa-play space-right" aria-hidden="true" />
<Button icon={Play} color="light" onClick={() => {}}>
Start
</Button>
<Button color="primary" onClick={() => {}}>
<i className="fa fa-sync space-right" aria-hidden="true" />
<Button icon={RefreshCw} color="primary" onClick={() => {}}>
Restart
</Button>
</ButtonGroup>

View file

@ -1,5 +1,6 @@
import { PropsWithChildren } from 'react';
import clsx from 'clsx';
import { Check, Copy } from 'lucide-react';
import { Icon } from '@@/Icon';
@ -33,7 +34,7 @@ export function CopyButton({
title="Copy Value"
type="button"
>
<Icon icon="copy" feather />
<Icon icon={Copy} />
{children}
</Button>
@ -45,7 +46,7 @@ export function CopyButton({
'vertical-center'
)}
>
<Icon icon="check" feather />
<Icon icon={Check} />
{displayText && <span className="space-left">{displayText}</span>}
</span>
</div>

View file

@ -1,4 +1,5 @@
import { Meta } from '@storybook/react';
import { Download } from 'lucide-react';
import { LoadingButton } from './LoadingButton';
@ -14,8 +15,12 @@ interface Args {
function Template({ loadingText, isLoading }: Args) {
return (
<LoadingButton loadingText={loadingText} isLoading={isLoading}>
<i className="fa fa-download" aria-hidden="true" /> Download
<LoadingButton
loadingText={loadingText}
isLoading={isLoading}
icon={Download}
>
Download
</LoadingButton>
);
}
@ -29,8 +34,8 @@ export const Example = Template.bind({});
export function IsLoading() {
return (
<LoadingButton loadingText="loading" isLoading>
<i className="fa fa-download" aria-hidden="true" /> Download
<LoadingButton loadingText="loading" isLoading icon={Download}>
Download
</LoadingButton>
);
}

View file

@ -6,18 +6,18 @@ test('when isLoading is true should show spinner and loading text', async () =>
const loadingText = 'loading';
const children = 'not visible';
const { findByLabelText, queryByText, findByText } = render(
const { queryByText, findByText, container } = render(
<LoadingButton loadingText={loadingText} isLoading>
{children}
</LoadingButton>
);
const spinner = container.querySelector('svg');
expect(spinner).toBeVisible();
const buttonLabel = queryByText(children);
expect(buttonLabel).toBeNull();
const spinner = await findByLabelText('loading');
expect(spinner).toBeVisible();
const loadingTextElem = await findByText(loadingText);
expect(loadingTextElem).toBeVisible();
});
@ -26,7 +26,7 @@ test('should show children when false', async () => {
const loadingText = 'loading';
const children = 'visible';
const { queryByLabelText, queryByText } = render(
const { queryByText, container } = render(
<LoadingButton loadingText={loadingText} isLoading={false}>
{children}
</LoadingButton>
@ -35,7 +35,7 @@ test('should show children when false', async () => {
const buttonLabel = queryByText(children);
expect(buttonLabel).toBeVisible();
const spinner = queryByLabelText('loading');
const spinner = container.querySelector('svg');
expect(spinner).toBeNull();
const loadingTextElem = queryByText(loadingText);

View file

@ -1,4 +1,7 @@
import { PropsWithChildren } from 'react';
import { PropsWithChildren, ReactNode } from 'react';
import { Loader2 } from 'lucide-react';
import { Icon } from '@@/Icon';
import { type Props as ButtonProps, Button } from './Button';
@ -13,6 +16,7 @@ export function LoadingButton({
disabled,
type = 'submit',
children,
icon,
...buttonProps
}: PropsWithChildren<Props>) {
return (
@ -21,19 +25,22 @@ export function LoadingButton({
{...buttonProps}
type={type}
disabled={disabled || isLoading}
icon={loadingButtonIcon(isLoading, icon)}
>
{isLoading ? (
<>
<i
className="fa fa-circle-notch fa-spin space-right"
aria-label="loading"
aria-hidden="true"
/>
{loadingText}
</>
) : (
children
)}
{isLoading ? loadingText : children}
</Button>
);
}
function loadingButtonIcon(isLoading: boolean, defaultIcon: ReactNode) {
if (!isLoading) {
return defaultIcon;
}
return (
<Icon
icon={Loader2}
className="animate-spin-slow ml-1"
aria-label="loading"
/>
);
}

View file

@ -1,6 +1,9 @@
import { ReactNode } from 'react';
import clsx from 'clsx';
import { Menu, MenuList, MenuButton } from '@reach/menu-button';
import { MoreVertical } from 'lucide-react';
import { Icon } from '@@/Icon';
import styles from './ActionsMenu.module.css';
@ -19,7 +22,7 @@ export function ActionsMenu({ children }: Props) {
isExpanded && styles.actionsActive
)}
>
<i className="fa fa-ellipsis-v" aria-hidden="true" />
<Icon icon={MoreVertical} />
</MenuButton>
<MenuList>
<div className={styles.tableActionsMenuList}>{children}</div>

View file

@ -1,7 +1,7 @@
import clsx from 'clsx';
import { Menu, MenuButton, MenuList } from '@reach/menu-button';
import { ColumnInstance } from 'react-table';
import { Columns } from 'react-feather';
import { Columns } from 'lucide-react';
import { Checkbox } from '@@/form-components/Checkbox';

View file

@ -1,5 +1,8 @@
import { PropsWithChildren } from 'react';
import { Row } from 'react-table';
import { ChevronDown, ChevronRight } from 'lucide-react';
import { Icon } from '@@/Icon';
import styles from './ExpandingCell.module.css';
@ -15,22 +18,15 @@ export function ExpandingCell<
<>
{showExpandArrow && (
<button type="button" className={styles.expandButton}>
<i
<Icon
// eslint-disable-next-line react/jsx-props-no-spreading
{...row.getToggleRowExpandedProps()}
className={`fas ${arrowClass(row.isExpanded)} space-right`}
aria-hidden="true"
icon={row.isExpanded ? ChevronDown : ChevronRight}
className="mr-1"
/>
</button>
)}
{children}
</>
);
function arrowClass(isExpanded: boolean) {
if (isExpanded) {
return 'fa-angle-down';
}
return 'fa-angle-right';
}
}

View file

@ -2,6 +2,9 @@ import clsx from 'clsx';
import { useMemo } from 'react';
import { Menu, MenuButton, MenuPopover } from '@reach/menu-button';
import { ColumnInstance } from 'react-table';
import { Check, Filter } from 'lucide-react';
import { Icon } from '@@/Icon';
export const DefaultFilter = filterHOC('Filter by state');
@ -25,17 +28,12 @@ export function MultipleSelectionFilter({
<div>
<Menu>
<MenuButton
className={clsx('table-filter', { 'filter-active': enabled })}
className={clsx('table-filter flex items-center', {
'filter-active': enabled,
})}
>
Filter
<i
className={clsx(
'fa',
'space-left',
enabled ? 'fa-check' : 'fa-filter'
)}
aria-hidden="true"
/>
<Icon icon={enabled ? Check : Filter} className="!ml-1" />
</MenuButton>
<MenuPopover className="dropdown-menu">
<div className="tableMenu">

View file

@ -1,4 +1,4 @@
import { Search } from 'react-feather';
import { Search } from 'lucide-react';
import { useLocalStorage } from '@/react/hooks/useLocalStorage';
@ -15,7 +15,7 @@ export function FilterSearchBar({
}: Props) {
return (
<div className="searchBar items-center flex h-[34px]">
<Search className="searchIcon feather" />
<Search className="searchIcon lucide" />
<input
type="text"
className="searchInput"

View file

@ -1,4 +1,4 @@
import { Search } from 'react-feather';
import { Search } from 'lucide-react';
import { useLocalStorage } from '@/react/hooks/useLocalStorage';
import { AutomationTestingProps } from '@/types';
@ -20,7 +20,7 @@ export function SearchBar({
return (
<div className="searchBar items-center flex min-w-[90px]">
<Search className="searchIcon feather shrink-0" />
<Search className="searchIcon lucide shrink-0" />
<input
type="text"
className="searchInput"

View file

@ -76,6 +76,7 @@ function SortWrapper({
<TableHeaderSortIcons
sorted={isSorted}
descending={isSorted && !!isSortedDesc}
className="ml-1"
/>
</div>
</button>

View file

@ -7,14 +7,15 @@ import styles from './TableHeaderSortIcons.module.css';
interface Props {
sorted: boolean;
descending: boolean;
className?: string;
}
export function TableHeaderSortIcons({ sorted, descending }: Props) {
export function TableHeaderSortIcons({ sorted, descending, className }: Props) {
return (
<div className="flex flex-row no-wrap w-min-max">
<div className="flex flex-row no-wrap w-min-max align-middle">
<SortDownIcon
className={clsx(
'space-left',
className,
sorted && !descending && styles.activeSortIcon,
styles.sortIcon
)}

View file

@ -1,7 +1,7 @@
import clsx from 'clsx';
import { Menu, MenuButton, MenuList } from '@reach/menu-button';
import { PropsWithChildren, ReactNode } from 'react';
import { MoreVertical } from 'react-feather';
import { MoreVertical } from 'lucide-react';
interface Props {
quickActions?: ReactNode;

View file

@ -1,7 +1,9 @@
import clsx from 'clsx';
import { useState } from 'react';
import { Check } from 'lucide-react';
import { Checkbox } from '@@/form-components/Checkbox';
import { Icon } from '@@/Icon';
import styles from './TableSettingsMenuAutoRefresh.module.css';
@ -46,12 +48,7 @@ export function TableSettingsMenuAutoRefresh({ onChange, value }: Props) {
)}
onTransitionEnd={() => setIsCheckVisible(false)}
>
<i
id="refreshRateChange"
className="fa fa-check green-icon"
aria-hidden="true"
style={{ marginTop: '7px' }}
/>
<Icon icon={Check} className="!ml-1" mode="success" />
</span>
</div>
)}

View file

@ -4,14 +4,12 @@ import { Icon } from '@@/Icon';
interface Props {
icon?: ReactNode | ComponentType<unknown>;
featherIcon?: boolean;
label: string;
description?: ReactNode;
}
export function TableTitle({
icon,
featherIcon,
label,
children,
description,
@ -22,11 +20,7 @@ export function TableTitle({
<div className="toolBarTitle">
{icon && (
<div className="widget-icon">
<Icon
icon={icon}
feather={featherIcon}
className="space-right feather"
/>
<Icon icon={icon} className="space-right" />
</div>
)}

View file

@ -1,4 +1,4 @@
import { ChevronDown, ChevronUp } from 'react-feather';
import { ChevronDown, ChevronUp } from 'lucide-react';
import { CellProps, Column, HeaderProps } from 'react-table';
import { Button } from '@@/buttons';

View file

@ -1,4 +1,5 @@
import { ChangeEvent, createRef } from 'react';
import { XCircle } from 'lucide-react';
import { Button } from '@@/buttons';
import { Icon } from '@@/Icon';
@ -46,7 +47,7 @@ export function FileUploadField({
</Button>
<span className="vertical-center">
{value ? value.name : <Icon icon="x-circle" feather mode="danger" />}
{value ? value.name : <Icon icon={XCircle} mode="danger" />}
</span>
</div>
);

View file

@ -11,11 +11,11 @@ export type Size = 'xsmall' | 'small' | 'medium' | 'large';
export interface Props {
inputId?: string;
label: string | ReactNode;
label: ReactNode;
size?: Size;
tooltip?: string;
children: ReactNode;
errors?: string | ReactNode;
errors?: ReactNode;
required?: boolean;
}

View file

@ -1,5 +1,6 @@
import { PropsWithChildren } from 'react';
import clsx from 'clsx';
import { AlertTriangle } from 'lucide-react';
import { Icon } from '@@/Icon';
@ -10,7 +11,7 @@ interface Props {
export function FormError({ children, className }: PropsWithChildren<Props>) {
return (
<p className={clsx(`text-muted small vertical-center`, className)}>
<Icon icon="alert-triangle" className="icon-warning" feather />
<Icon icon={AlertTriangle} className="icon-warning" />
<span className="text-warning">{children}</span>
</p>
);

View file

@ -1,4 +1,5 @@
import { PropsWithChildren, useState } from 'react';
import { ChevronDown, ChevronRight } from 'lucide-react';
import { Icon } from '@@/Icon';
@ -27,9 +28,8 @@ export function FormSection({
className="border-0 mx-2 !ml-0 bg-transparent inline-flex justify-center items-center w-2"
>
<Icon
icon={isExpanded ? 'chevron-down' : 'chevron-right'}
icon={isExpanded ? ChevronDown : ChevronRight}
className="shrink-0"
feather
/>
</button>
)}

View file

@ -1,9 +1,9 @@
import { ComponentType } from 'react';
import clsx from 'clsx';
import { FormikErrors } from 'formik';
import { ArrowDown, ArrowUp, Trash2 } from 'lucide-react';
import { AddButton, Button } from '@@/buttons';
import { Icon } from '@@/Icon';
import { Tooltip } from '@@/Tip/Tooltip';
import { TextTip } from '@@/Tip/TextTip';
@ -136,18 +136,16 @@ export function InputList<T = DefaultType>({
disabled={disabled || index === 0}
onClick={() => handleMoveUp(index)}
className="vertical-center btn-only-icon"
>
<Icon icon="arrow-up" feather />
</Button>
icon={ArrowUp}
/>
<Button
size="medium"
type="button"
disabled={disabled || index === value.length - 1}
onClick={() => handleMoveDown(index)}
className="vertical-center btn-only-icon"
>
<Icon icon="arrow-down" feather />
</Button>
icon={ArrowDown}
/>
</>
)}
{!readOnly && (
@ -156,9 +154,8 @@ export function InputList<T = DefaultType>({
size="medium"
onClick={() => handleRemoveItem(key, item)}
className="vertical-center btn-only-icon"
>
<Icon icon="trash-2" feather size="md" />
</Button>
icon={Trash2}
/>
)}
</div>
</div>

View file

@ -32,6 +32,10 @@
padding: 0 8px;
}
.portainer-selector-root .portainer-selector__clear-indicator {
padding: 6px;
}
.portainer-selector-root .portainer-selector__multi-value__label {
@apply text-black;
@apply th-dark:text-white;

View file

@ -1,4 +1,4 @@
import clsx from 'clsx';
import { Heart, Power } from 'lucide-react';
import { Icon } from '@/react/components/Icon';
@ -17,37 +17,21 @@ export function ContainerStatus({ containers }: Props) {
<div className="pull-right">
<div>
<div className="vertical-center space-right pr-5">
<Icon
icon="power"
className={clsx('icon icon-sm icon-success')}
feather
/>
<Icon icon={Power} mode="success" size="sm" />
{runningContainersFilter(containers)} running
</div>
<div className="vertical-center space-right">
<Icon
icon="power"
className={clsx('icon icon-sm icon-danger')}
feather
/>
<Icon icon={Power} mode="danger" size="sm" />
{stoppedContainersFilter(containers)} stopped
</div>
</div>
<div>
<div className="vertical-center space-right pr-5">
<Icon
icon="heart"
className={clsx('icon icon-sm icon-success')}
feather
/>
<Icon icon={Heart} mode="success" size="sm" />
{healthyContainersFilter(containers)} healthy
</div>
<div className="vertical-center space-right">
<Icon
icon="heart"
className={clsx('icon icon-sm icon-danger')}
feather
/>
<Icon icon={Heart} mode="danger" size="sm" />
{unhealthyContainersFilter(containers)} unhealthy
</div>
</div>

View file

@ -1,4 +1,5 @@
import clsx from 'clsx';
import { PieChart } from 'lucide-react';
import { Icon } from '@/react/components/Icon';
import { humanize } from '@/portainer/filters/filters';
@ -14,7 +15,7 @@ export function useImagesTotalSizeComponent(imagesTotalSize: number) {
export function ImagesTotalSize({ imagesTotalSize }: Props) {
return (
<div className="vertical-center">
<Icon icon="pie-chart" className={clsx('space-right')} feather />
<Icon icon={PieChart} className={clsx('space-right')} />
{humanize(imagesTotalSize)}
</div>
);

View file

@ -1,3 +1,7 @@
import clsx from 'clsx';
import { TableHeaderSortIcons } from '@@/datatables/TableHeaderSortIcons';
import { TemplateListDropdown } from '../TemplateListDropdown';
import styles from './TemplateListSort.module.css';
@ -21,10 +25,6 @@ export function TemplateListSort({
sortByButton,
value,
}: Props) {
const upIcon = 'fa fa-sort-alpha-up';
const downIcon = 'fa fa-sort-alpha-down';
const iconStyle = sortByDescending ? upIcon : downIcon;
return (
<div className={styles.sortByContainer}>
<div className={styles.sortByElement}>
@ -37,7 +37,7 @@ export function TemplateListSort({
</div>
<div className={styles.sortByElement}>
<button
className={styles.sortButton}
className={clsx(styles.sortButton, 'h-[34px]')}
type="button"
disabled={!sortByButton || !value}
onClick={(e) => {
@ -45,7 +45,10 @@ export function TemplateListSort({
onDescending();
}}
>
<i className={iconStyle} />
<TableHeaderSortIcons
sorted={sortByButton && !!value}
descending={sortByDescending}
/>
</button>
</div>
</div>

View file

@ -1,5 +1,5 @@
import { ComponentProps } from 'react';
import { Server } from 'react-feather';
import { HeartPulse, Server } from 'lucide-react';
import { TableContainer, TableTitle } from '@@/datatables';
import { DetailsTable } from '@@/DetailsTable';
@ -31,7 +31,7 @@ export function HealthStatus({ health }: Props) {
<DetailsTable.Row label="Status">
<div className="vertical-center">
<Icon
icon="fa fa-heartbeat"
icon={HeartPulse}
mode={StatusMode[health.Status]}
className="space-right"
/>

View file

@ -1,6 +1,6 @@
import _ from 'lodash';
import { useStore } from 'zustand';
import { Box } from 'react-feather';
import { Box } from 'lucide-react';
import { Environment } from '@/react/portainer/environments/types';
import type { DockerContainer } from '@/react/docker/containers/types';

View file

@ -7,7 +7,7 @@ import {
Slash,
Square,
Trash2,
} from 'react-feather';
} from 'lucide-react';
import * as notifications from '@/portainer/services/notifications';
import { useAuthorizations, Authorized } from '@/react/hooks/useUser';

View file

@ -1,8 +1,11 @@
import { Column } from 'react-table';
import _ from 'lodash';
import { ExternalLink } from 'lucide-react';
import type { DockerContainer, Port } from '@/react/docker/containers/types';
import { Icon } from '@@/Icon';
import { useRowContext } from '../RowContext';
export const ports: Column<DockerContainer> = {
@ -37,7 +40,7 @@ function PortsCell({ value: ports }: Props) {
target="_blank"
rel="noreferrer"
>
<i className="fa fa-external-link-alt" aria-hidden="true" />
<Icon icon={ExternalLink} />
{port.public}:{port.private}
</a>
));

View file

@ -1,4 +1,5 @@
import clsx from 'clsx';
import { BarChart, FileText, Info, Paperclip, Terminal } from 'lucide-react';
import { ContainerStatus } from '@/react/docker/containers/types';
import { Authorized } from '@/react/hooks/useUser';
@ -51,7 +52,7 @@ export function ContainerQuickActions({
params={{ id: containerId, nodeName }}
title="Logs"
>
<Icon icon="file-text" feather className="space-right" />
<Icon icon={FileText} className="space-right" />
</Link>
</Authorized>
)}
@ -63,7 +64,7 @@ export function ContainerQuickActions({
params={{ id: containerId, nodeName }}
title="Inspect"
>
<Icon icon="info" feather className="space-right" />
<Icon icon={Info} className="space-right" />
</Link>
</Authorized>
)}
@ -75,7 +76,7 @@ export function ContainerQuickActions({
params={{ id: containerId, nodeName }}
title="Stats"
>
<Icon icon="bar-chart" feather className="space-right" />
<Icon icon={BarChart} className="space-right" />
</Link>
</Authorized>
)}
@ -87,7 +88,7 @@ export function ContainerQuickActions({
params={{ id: containerId, nodeName }}
title="Exec Console"
>
<Icon icon="terminal" feather className="space-right" />
<Icon icon={Terminal} className="space-right" />
</Link>
</Authorized>
)}
@ -99,7 +100,7 @@ export function ContainerQuickActions({
params={{ id: containerId, nodeName }}
title="Attach Console"
>
<Icon icon="paperclip" feather className="space-right" />
<Icon icon={Paperclip} className="space-right" />
</Link>
</Authorized>
)}
@ -122,7 +123,7 @@ function TaskQuickActions({ taskId, state }: TaskProps) {
params={{ id: taskId }}
title="Logs"
>
<Icon icon="file-text" feather className="space-right" />
<Icon icon={FileText} className="space-right" />
</Link>
</Authorized>
)}
@ -130,7 +131,7 @@ function TaskQuickActions({ taskId, state }: TaskProps) {
{state.showQuickActionInspect && (
<Authorized authorizations="DockerTaskInspect">
<Link to="docker.tasks.task" params={{ id: taskId }} title="Inspect">
<Icon icon="info" feather className="space-right" />
<Icon icon={Info} className="space-right" />
</Link>
</Authorized>
)}

View file

@ -1,4 +1,4 @@
import { List } from 'react-feather';
import { List } from 'lucide-react';
import { joinCommand } from '@/docker/filters/utils';
import { getPairKey, getPairValue } from '@/portainer/filters/filters';

View file

@ -1,3 +1,5 @@
import { Server, Trash2 } from 'lucide-react';
import { Authorized } from '@/react/hooks/useUser';
import { EnvironmentId } from '@/react/portainer/environments/types';
import { Icon } from '@/react/components/Icon';
@ -39,7 +41,7 @@ export function NetworkContainersTable({
return (
<TableContainer>
<TableTitle label="Containers in network" icon="server" featherIcon />
<TableTitle label="Containers in network" icon={Server} />
<Table className="nopadding">
<DetailsTable
headers={tableHeaders}
@ -78,11 +80,7 @@ export function NetworkContainersTable({
}
}}
>
<Icon
icon="trash-2"
feather
class-name="icon-secondary icon-md"
/>
<Icon icon={Trash2} class-name="icon-secondary icon-md" />
Leave Network
</Button>
</Authorized>

View file

@ -1,4 +1,5 @@
import { Fragment } from 'react';
import { Share2, Trash2 } from 'lucide-react';
import DockerNetworkHelper from '@/docker/helpers/networkHelper';
import { Authorized } from '@/react/hooks/useUser';
@ -30,7 +31,7 @@ export function NetworkDetailsTable({
return (
<TableContainer>
<TableTitle label="Network details" icon="share-2" featherIcon />
<TableTitle label="Network details" icon={Share2} />
<Table className="nopadding">
<DetailsTable dataCy="networkDetails-detailsTable">
{/* networkRowContent */}
@ -46,8 +47,7 @@ export function NetworkDetailsTable({
onClick={() => onRemoveNetworkClicked()}
>
<Icon
icon="trash-2"
feather
icon={Trash2}
className="space-right"
aria-hidden="true"
/>

View file

@ -1,3 +1,5 @@
import { Share2 } from 'lucide-react';
import { Table, TableContainer, TableTitle } from '@@/datatables';
import { DetailsTable } from '@@/DetailsTable';
@ -16,7 +18,7 @@ export function NetworkOptionsTable({ options }: Props) {
return (
<TableContainer>
<TableTitle label="Network options" icon="share-2" featherIcon />
<TableTitle label="Network options" icon={Share2} />
<Table className="nopadding">
<DetailsTable dataCy="networkDetails-networkOptionsTable">
{networkEntries.map(([key, value]) => (

View file

@ -1,6 +1,6 @@
import _ from 'lodash';
import { useStore } from 'zustand';
import { Box } from 'react-feather';
import { Box } from 'lucide-react';
import { DockerContainer } from '@/react/docker/containers/types';
import { Environment } from '@/react/portainer/environments/types';

View file

@ -1,4 +1,9 @@
import { LayoutGrid } from 'lucide-react';
import Linux from '@/assets/ico/linux.svg?c';
import { ButtonSelector } from '@@/form-components/ButtonSelector/ButtonSelector';
import { Icon } from '@@/Icon';
import { OS } from './types';
@ -20,7 +25,7 @@ export function OsSelector({ onChange, value }: Props) {
value: 'linux',
label: (
<>
<i className="fab fa-linux space-right" aria-hidden="true" />
<Icon icon={Linux} className="mr-1" />
Linux
</>
),
@ -29,10 +34,7 @@ export function OsSelector({ onChange, value }: Props) {
value: 'win',
label: (
<>
<i
className="fab fa-windows space-right"
aria-hidden="true"
/>
<Icon icon={LayoutGrid} className="mr-1" />
Windows
</>
),

View file

@ -1,8 +1,11 @@
import { CellProps, Column } from 'react-table';
import clsx from 'clsx';
import { Settings } from 'lucide-react';
import { Device } from '@/portainer/hostmanagement/open-amt/model';
import { Icon } from '@@/Icon';
import { useRowContext } from './RowContext';
enum PowerState {
@ -48,7 +51,11 @@ export function PowerStateCell({
>
{parsePowerState(device.powerState)}
</span>
<span>{isLoading && <i className="fa fa-cog fa-spin space-left" />}</span>
<span>
{isLoading && (
<Icon icon={Settings} className="animate-spin-slow !ml-1" />
)}
</span>
</>
);
}

View file

@ -1,6 +1,6 @@
import _ from 'lodash';
import { useStore } from 'zustand';
import { Box } from 'react-feather';
import { Box } from 'lucide-react';
import { useState } from 'react';
import { EdgeTypes, Environment } from '@/react/portainer/environments/types';

View file

@ -1,4 +1,5 @@
import { useRouter } from '@uirouter/react';
import { Plus, Trash2, Link as LinkIcon } from 'lucide-react';
import type { Environment } from '@/react/portainer/environments/types';
import {
@ -40,13 +41,12 @@ export function EdgeDevicesDatatableActions({
disabled={selectedItems.length < 1}
color="danger"
onClick={() => onDeleteEdgeDeviceClick()}
icon="trash-2"
featherIcon
icon={Trash2}
>
Remove
</Button>
<Button onClick={() => onAddNewDeviceClick()} icon="plus" featherIcon>
<Button onClick={() => onAddNewDeviceClick()} icon={Plus}>
Add Device
</Button>
@ -54,8 +54,7 @@ export function EdgeDevicesDatatableActions({
<Button
disabled={selectedItems.length !== 1}
onClick={() => onAssociateOpenAMTClick(selectedItems)}
icon="link"
featherIcon
icon={LinkIcon}
>
Associate with OpenAMT
</Button>

View file

@ -1,5 +1,5 @@
import { useEffect, useState } from 'react';
import { Database } from 'react-feather';
import { AlertTriangle, Database } from 'lucide-react';
import { useStore } from 'zustand';
import { confirmWarn } from '@/portainer/services/modal.service/confirm';
@ -150,7 +150,7 @@ export function IngressClassDatatable({
ingControllerFormValues &&
isUnsavedChanges(ingressControllers, ingControllerFormValues) && (
<span className="flex items-center text-warning mt-1">
<Icon icon="alert-triangle" feather className="!mr-1" />
<Icon icon={AlertTriangle} className="!mr-1" />
<span className="text-warning">Unsaved changes.</span>
</span>
)}

View file

@ -1,4 +1,5 @@
import { CellProps, Column } from 'react-table';
import { Check, X } from 'lucide-react';
import { Badge } from '@@/Badge';
import { Icon } from '@@/Icon';
@ -20,7 +21,7 @@ export const availability: Column<IngressControllerClassMap> = {
function AvailailityCell({ value }: CellProps<IngressControllerClassMap>) {
return (
<Badge type={value ? 'success' : 'danger'}>
<Icon icon={value ? 'check' : 'x'} feather className="!mr-1" />
<Icon icon={value ? Check : X} className="!mr-1" />
{value ? 'Allowed' : 'Disallowed'}
</Badge>
);

View file

@ -1,7 +1,8 @@
import { ChangeEvent, ReactNode } from 'react';
import { Trash2 } from 'lucide-react';
import { Icon } from '@@/Icon';
import { FormError } from '@@/form-components/FormError';
import { Button } from '@@/buttons';
import { Annotation } from './types';
@ -69,13 +70,14 @@ export function Annotations({
)}
</div>
<div className="col-sm-3 !pl-0 !m-0">
<button
className="btn btn-sm btn-dangerlight btn-only-icon !ml-0"
<Button
size="small"
color="dangerlight"
className="btn-only-icon !ml-0"
type="button"
onClick={() => removeAnnotation(i)}
>
<Icon icon="trash-2" size="md" feather />
</button>
icon={Trash2}
/>
</div>
</div>
))}

View file

@ -1,5 +1,7 @@
import { ChangeEvent, ReactNode } from 'react';
import { Plus, RefreshCw, Trash2 } from 'react-feather';
import { Info, Plus, RefreshCw, Trash2 } from 'lucide-react';
import Route from '@/assets/ico/route.svg?c';
import { Link } from '@@/Link';
import { Icon } from '@@/Icon';
@ -107,7 +109,7 @@ export function IngressForm({
return (
<Widget>
<WidgetTitle icon="svg-route" title="Ingress" />
<WidgetTitle icon={Route} title="Ingress" />
<WidgetBody key={rule.Key + rule.Namespace}>
<div className="row">
<div className="form-horizontal">
@ -199,7 +201,7 @@ export function IngressForm({
<div className="col-sm-12 px-0 text-muted !mb-0">
<div className="mb-2">Annotations</div>
<p className="vertical-center text-muted small">
<Icon icon="info" mode="primary" feather />
<Icon icon={Info} mode="primary" />
<span>
You can specify{' '}
<a
@ -355,7 +357,7 @@ export function IngressForm({
</div>
<p className="vertical-center text-muted small whitespace-nowrap col-sm-12 !p-0">
<Icon icon="info" mode="primary" size="md" feather />
<Icon icon={Info} mode="primary" size="md" />
<span>
Add a secret via{' '}
<Link
@ -375,10 +377,10 @@ export function IngressForm({
)}
{host.NoHost && (
<p className="vertical-center text-muted small whitespace-nowrap col-sm-12 !p-0">
<Icon icon="info" mode="primary" size="md" feather />A
fallback rule has no host specified. This rule only applies
when an inbound request has a hostname that does not match
with any of your other rules.
<Icon icon={Info} mode="primary" size="md" />A fallback rule
has no host specified. This rule only applies when an
inbound request has a hostname that does not match with any
of your other rules.
</p>
)}

View file

@ -1,4 +1,4 @@
import { Plus, Trash2 } from 'react-feather';
import { Plus, Trash2 } from 'lucide-react';
import { useRouter } from '@uirouter/react';
import { useStore } from 'zustand';
@ -6,6 +6,7 @@ import { useEnvironmentId } from '@/react/hooks/useEnvironmentId';
import { useNamespaces } from '@/react/kubernetes/namespaces/queries';
import { useAuthorizations, Authorized } from '@/react/hooks/useUser';
import { confirmDeletionAsync } from '@/portainer/services/modal.service/confirm';
import Route from '@/assets/ico/route.svg?c';
import { Datatable } from '@@/datatables';
import { Button } from '@@/buttons';
@ -51,7 +52,7 @@ export function IngressDatatable() {
isLoading={ingressesQuery.isLoading}
emptyContentLabel="No supported ingresses found"
title="Ingresses"
titleIcon="svg-route"
titleIcon={Route}
getRowId={(row) => row.Name + row.Type + row.Namespace}
renderTableActions={tableActions}
disableSelect={useCheckboxes()}

View file

@ -1,4 +1,5 @@
import { CellProps, Column } from 'react-table';
import { AlertTriangle, ArrowRight } from 'lucide-react';
import { Icon } from '@@/Icon';
import { Badge } from '@@/Badge';
@ -34,11 +35,11 @@ export const ingressRules: Column<Ingress> = {
<div key={`${path.Host}${path.Path}${path.ServiceName}:${path.Port}`}>
<span className="flex px-2 flex-nowrap items-center gap-1">
{link(path.Host, path.Path, isHttp)}
<Icon icon="arrow-right" feather />
<Icon icon={ArrowRight} />
{`${path.ServiceName}:${path.Port}`}
{!path.HasService && (
<Badge type="warn" className="ml-1 gap-1">
<Icon icon="alert-triangle" feather />
<Icon icon={AlertTriangle} />
Service doesn&apos;t exist
</Badge>
)}

View file

@ -1,4 +1,4 @@
import { User as UserIcon, Users as TeamIcon } from 'react-feather';
import { User as UserIcon, Users as TeamIcon } from 'lucide-react';
import { OptionProps, components, MultiValueGenericProps } from 'react-select';
import { Select } from '@@/form-components/ReactSelect';

View file

@ -1,9 +1,12 @@
import { List, Settings, Boxes, Gauge } from 'lucide-react';
import { useEnvironmentId } from '@/react/hooks/useEnvironmentId';
import { DashboardItem } from '@@/DashboardItem';
import { Widget, WidgetTitle, WidgetBody } from '@@/Widget';
import { PageHeader } from '@@/PageHeader';
import { DashboardGrid } from '@@/DashboardItem/DashboardGrid';
import { Icon } from '@@/Icon';
import { useDashboard } from './useDashboard';
import { RunningStatus } from './RunningStatus';
@ -25,7 +28,7 @@ export function DashboardView() {
{dashboardQuery.isLoading ? (
<div className="text-center" style={{ marginTop: '30%' }}>
Connecting to the Edge environment...
<i className="fa fa-cog fa-spin space-left" />
<Icon icon={Settings} className="animate-spin-slow !ml-1" />
</div>
) : (
<>
@ -33,10 +36,7 @@ export function DashboardView() {
<div className="col-sm-12">
{/* cluster info */}
<Widget>
<WidgetTitle
icon="fa-tachometer-alt"
title="Cluster information"
/>
<WidgetTitle icon={Gauge} title="Cluster information" />
<WidgetBody className="no-padding">
<table className="table">
<tbody>
@ -56,19 +56,19 @@ export function DashboardView() {
{/* jobs */}
<DashboardItem
value={dashboardQuery.data?.JobCount}
icon="fa fa-th-list"
icon={List}
type="Nomad Job"
/>
{/* groups */}
<DashboardItem
value={dashboardQuery.data?.GroupCount}
icon="fa fa-list-alt"
icon={List}
type="Group"
/>
{/* tasks */}
<DashboardItem
value={dashboardQuery.data?.TaskCount}
icon="fa fa-cubes"
icon={Boxes}
type="Task"
>
{/* running status of tasks */}

View file

@ -1,3 +1,7 @@
import { Power } from 'lucide-react';
import { Icon } from '@@/Icon';
interface Props {
running: number;
stopped: number;
@ -7,17 +11,11 @@ export function RunningStatus({ running, stopped }: Props) {
return (
<div>
<div>
<i
className="fa fa-power-off green-icon space-right"
aria-hidden="true"
/>
<Icon icon={Power} mode="success" />
{`${running || '-'} running`}
</div>
<div>
<i
className="fa fa-power-off red-icon space-right"
aria-hidden="true"
/>
<Icon icon={Power} mode="danger" />
{`${stopped || '-'} stopped`}
</div>
</div>

View file

@ -1,3 +1,4 @@
import { History } from 'lucide-react';
import { useStore } from 'zustand';
import { NomadEvent } from '@/react/nomad/types';
@ -33,7 +34,7 @@ export function EventsDatatable({ data, isLoading }: EventsDatatableProps) {
onSortByChange={settings.setSortBy}
searchValue={search}
onSearchChange={setSearch}
titleIcon="fa-history"
titleIcon={History}
title="Events"
totalCount={data.length}
getRowId={(row) => `${row.Date}-${row.Message}-${row.Type}`}

View file

@ -1,5 +1,5 @@
import { useStore } from 'zustand';
import { Clock } from 'react-feather';
import { Clock } from 'lucide-react';
import { Job } from '@/react/nomad/types';

View file

@ -1,4 +1,5 @@
import { CellProps, Column } from 'react-table';
import { Clock, FileText } from 'lucide-react';
import { Task } from '@/react/nomad/types';
@ -34,12 +35,12 @@ export function ActionsCell({ row }: CellProps<Task>) {
title="Events"
className="space-right"
>
<Icon icon="clock" feather className="space-right icon" />
<Icon icon={Clock} className="space-right" />
</Link>
{/* logs */}
<Link to="nomad.logs" params={params} title="Logs">
<Icon icon="file-text" feather className="space-right icon" />
<Icon icon={FileText} className="space-right" />
</Link>
</div>
);

View file

@ -1,4 +1,5 @@
import { useMutation } from 'react-query';
import { Trash2 } from 'lucide-react';
import { useEnvironmentId } from '@/react/hooks/useEnvironmentId';
import { Job } from '@/react/nomad/types';
@ -25,8 +26,8 @@ export function JobActions({ selectedItems, refreshData }: Props) {
disabled={selectedItems.length < 1 || mutation.isLoading}
color="danger"
onClick={handleDeleteClicked}
icon={Trash2}
>
<i className="fa fa-trash-alt space-right" aria-hidden="true" />
Remove
</LoadingButton>
);

View file

@ -1,5 +1,5 @@
import { CellProps, Column } from 'react-table';
import { Clock } from 'react-feather';
import { Clock } from 'lucide-react';
import { Job } from '@/react/nomad/types';
@ -19,7 +19,7 @@ export function ActionsCell({ row }: CellProps<Job>) {
return (
// eslint-disable-next-line react/jsx-props-no-spreading
<div className="text-center" {...row.getToggleRowExpandedProps()}>
<Clock className="feather" />
<Clock className="lucide" />
</div>
);
}

View file

@ -1,4 +1,7 @@
import clsx from 'clsx';
import { Settings } from 'lucide-react';
import { Icon } from '@@/Icon';
import styles from './EdgeLoadingSpinner.module.css';
@ -6,7 +9,7 @@ export function EdgeLoadingSpinner() {
return (
<div className={clsx('row', styles.root)}>
Connecting to the Edge environment...
<i className="fa fa-cog fa-spin space-left" />
<Icon icon={Settings} className="animate-spin-slow !ml-1" />
</div>
);
}

View file

@ -1,4 +1,4 @@
import { Zap } from 'react-feather';
import { Zap } from 'lucide-react';
import { EnvironmentType } from '@/react/portainer/environments/types';
import {
@ -19,7 +19,7 @@ export function AgentVersionTag({ type, version }: Props) {
return (
<span className="space-x-1">
<span>
+ <Zap className="icon icon-xs vertical-center" aria-hidden="true" />
<Zap className="icon icon-xs vertical-center" aria-hidden="true" />
</span>
<span>{isEdgeEnvironment(type) ? 'Edge Agent' : 'Agent'}</span>

View file

@ -1,5 +1,3 @@
import clsx from 'clsx';
import { environmentTypeIcon } from '@/portainer/filters/filters';
import dockerEdge from '@/assets/images/edge_endpoint.png';
import kube from '@/assets/images/kubernetes_endpoint.png';
@ -8,6 +6,8 @@ import { EnvironmentType } from '@/react/portainer/environments/types';
import azure from '@/assets/ico/vendor/azure.svg';
import docker from '@/assets/ico/vendor/docker.svg';
import { Icon } from '@@/Icon';
interface Props {
type: EnvironmentType;
}
@ -17,7 +17,7 @@ export function EnvironmentIcon({ type }: Props) {
case EnvironmentType.AgentOnDocker:
case EnvironmentType.Docker:
return (
<img src={docker} width="60" alt="azure endpoint" aria-hidden="true" />
<img src={docker} width="60" alt="docker endpoint" aria-hidden="true" />
);
case EnvironmentType.Azure:
return (
@ -36,9 +36,9 @@ export function EnvironmentIcon({ type }: Props) {
);
default:
return (
<i
className={clsx('fa-4x', 'blue-icon', environmentTypeIcon(type))}
aria-hidden="true"
<Icon
icon={environmentTypeIcon(type)}
className="blue-icon !h-16 !w-16"
/>
);
}

View file

@ -1,6 +1,6 @@
import clsx from 'clsx';
import _ from 'lodash';
import { Edit2, Tag, Cpu } from 'react-feather';
import { Edit2, Tag, Cpu } from 'lucide-react';
import {
isoDateFromTimestamp,
@ -19,6 +19,7 @@ import {
import type { TagId } from '@/portainer/tags/types';
import { useTags } from '@/portainer/tags/queries';
import { useUser } from '@/react/hooks/useUser';
import Memory from '@/assets/ico/memory.svg?c';
import { Icon } from '@@/Icon';
import { Link } from '@@/Link';
@ -106,7 +107,7 @@ export function EnvironmentItem({ environment, onClick, groupName }: Props) {
/>
{environment.Snapshots[0].TotalCPU} CPU
<Icon
icon="svg-memory"
icon={Memory}
className="icon icon-sm space-right"
/>
{humanize(environment.Snapshots[0].TotalMemory)} RAM

View file

@ -1,3 +1,14 @@
import {
Layers,
Shuffle,
Database,
List,
HardDrive,
Box,
Power,
Heart,
} from 'lucide-react';
import {
DockerSnapshot,
EnvironmentType,
@ -5,7 +16,7 @@ import {
import { addPlural } from '@/portainer/helpers/strings';
import { AgentVersionTag } from './AgentVersionTag';
import { Stat } from './EnvironmentStatsItem';
import { EnvironmentStatsItem } from './EnvironmentStatsItem';
interface Props {
snapshots: DockerSnapshot[];
@ -31,17 +42,15 @@ export function EnvironmentStatsDocker({
return (
<div className="blocklist-item-line endpoint-item">
<span className="blocklist-item-desc">
<Stat
<EnvironmentStatsItem
value={addPlural(snapshot.StackCount, 'stack')}
icon="layers"
featherIcon
icon={Layers}
/>
{!!snapshot.Swarm && (
<Stat
<EnvironmentStatsItem
value={addPlural(snapshot.ServiceCount, 'service')}
icon="shuffle"
featherIcon
icon={Shuffle}
/>
)}
@ -51,15 +60,13 @@ export function EnvironmentStatsDocker({
healthy={snapshot.HealthyContainerCount}
unhealthy={snapshot.UnhealthyContainerCount}
/>
<Stat
<EnvironmentStatsItem
value={addPlural(snapshot.VolumeCount, 'volume')}
icon="database"
featherIcon
icon={Database}
/>
<Stat
<EnvironmentStatsItem
value={addPlural(snapshot.ImageCount, 'image')}
icon="list"
featherIcon
icon={List}
/>
</span>
@ -68,10 +75,9 @@ export function EnvironmentStatsDocker({
{snapshot.Swarm ? 'Swarm' : 'Standalone'} {snapshot.DockerVersion}
</span>
{snapshot.Swarm && (
<Stat
<EnvironmentStatsItem
value={addPlural(snapshot.NodeCount, 'node')}
icon="hard-drive"
featherIcon
icon={HardDrive}
/>
)}
<AgentVersionTag version={agentVersion} type={type} />
@ -96,39 +102,34 @@ function ContainerStats({
const containersCount = running + stopped;
return (
<Stat
<EnvironmentStatsItem
value={addPlural(containersCount, 'container')}
icon="box"
featherIcon
icon={Box}
>
{containersCount > 0 && (
<span className="space-x-2 space-right">
<Stat
<EnvironmentStatsItem
value={running}
icon="power"
featherIcon
icon={Power}
iconClass="icon-success"
/>
<Stat
<EnvironmentStatsItem
value={stopped}
icon="power"
featherIcon
icon={Power}
iconClass="icon-danger"
/>
<Stat
<EnvironmentStatsItem
value={healthy}
icon="heart"
featherIcon
icon={Heart}
iconClass="icon-success"
/>
<Stat
<EnvironmentStatsItem
value={unhealthy}
icon="heart"
featherIcon
icon={Heart}
iconClass="icon-warning"
/>
</span>
)}
</Stat>
</EnvironmentStatsItem>
);
}

View file

@ -5,15 +5,14 @@ import { Icon, IconProps } from '@/react/components/Icon';
interface Props extends IconProps {
value: string | number;
icon: string;
icon: IconProps['icon'];
iconClass?: string;
}
export function Stat({
export function EnvironmentStatsItem({
value,
icon,
children,
featherIcon,
iconClass,
}: PropsWithChildren<Props>) {
return (
@ -21,7 +20,6 @@ export function Stat({
<Icon
className={clsx('icon icon-sm space-right', iconClass)}
icon={icon}
feather={featherIcon}
/>
<span>{value}</span>
{children && <span className="space-left">{children}</span>}

Some files were not shown because too many files have changed in this diff Show more