diff --git a/packages/manager/.changeset/pr-12398-upcoming-features-1750738584786.md b/packages/manager/.changeset/pr-12398-upcoming-features-1750738584786.md new file mode 100644 index 00000000000..a9389e1da0a --- /dev/null +++ b/packages/manager/.changeset/pr-12398-upcoming-features-1750738584786.md @@ -0,0 +1,5 @@ +--- +"@linode/manager": Upcoming Features +--- + +Add new maintenance policy icons to linode rows ([#12398](https://github.com/linode/manager/pull/12398)) diff --git a/packages/manager/src/features/Account/Maintenance/MaintenanceTable.tsx b/packages/manager/src/features/Account/Maintenance/MaintenanceTable.tsx index 1f49ce53544..3582a309c7f 100644 --- a/packages/manager/src/features/Account/Maintenance/MaintenanceTable.tsx +++ b/packages/manager/src/features/Account/Maintenance/MaintenanceTable.tsx @@ -58,7 +58,7 @@ const useStyles = makeStyles()(() => ({ export type MaintenanceTableType = | 'completed' | 'in progress' - | 'pending' // TODO VM & Host Maintenance: Remove pending type after GA + | 'pending' | 'upcoming'; interface Props { diff --git a/packages/manager/src/features/Account/Maintenance/utilities.ts b/packages/manager/src/features/Account/Maintenance/utilities.ts index 80639cd2633..31b1dd3449b 100644 --- a/packages/manager/src/features/Account/Maintenance/utilities.ts +++ b/packages/manager/src/features/Account/Maintenance/utilities.ts @@ -16,6 +16,10 @@ export const PENDING_MAINTENANCE_FILTER = Object.freeze({ status: { '+or': ['pending', 'started', 'scheduled'] }, }); +export const PENDING_AND_IN_PROGRESS_MAINTENANCE_FILTER = Object.freeze({ + status: { '+or': ['pending', 'started', 'scheduled', 'in-progress'] }, +}); + export const PLATFORM_MAINTENANCE_TYPE = 'security_reboot_maintenance_scheduled'; diff --git a/packages/manager/src/features/Linodes/LinodesLanding/LinodeRow/LinodeRow.styles.ts b/packages/manager/src/features/Linodes/LinodesLanding/LinodeRow/LinodeRow.styles.ts index 24bb2ee8d9b..b523f63ff71 100644 --- a/packages/manager/src/features/Linodes/LinodesLanding/LinodeRow/LinodeRow.styles.ts +++ b/packages/manager/src/features/Linodes/LinodesLanding/LinodeRow/LinodeRow.styles.ts @@ -52,11 +52,6 @@ export const StyledMaintenanceTableCell = styled(TableCell, { minWidth: 200, }, }, - '& button': { - color: theme.textColors.linkActiveLight, - padding: '0 6px', - position: 'relative', - }, [theme.breakpoints.up('md')]: { width: '20%', }, diff --git a/packages/manager/src/features/Linodes/LinodesLanding/LinodeRow/LinodeRow.tsx b/packages/manager/src/features/Linodes/LinodesLanding/LinodeRow/LinodeRow.tsx index ce1cd0a2a65..5b7b96782fd 100644 --- a/packages/manager/src/features/Linodes/LinodesLanding/LinodeRow/LinodeRow.tsx +++ b/packages/manager/src/features/Linodes/LinodesLanding/LinodeRow/LinodeRow.tsx @@ -1,5 +1,12 @@ import { useTypeQuery } from '@linode/queries'; -import { Tooltip, TooltipIcon, Typography } from '@linode/ui'; +import { + LoadFailureIcon as MaintenanceActiveIcon, + CalendarIcon as MaintenancePendingIcon, + CalendarScheduledIcon as MaintenanceScheduledIcon, + Tooltip, + TooltipIcon, + Typography, +} from '@linode/ui'; import { Hidden } from '@linode/ui'; import { formatStorageUnits, getFormattedStatus } from '@linode/utilities'; import * as React from 'react'; @@ -32,6 +39,30 @@ import type { LinodeHandlers } from '../LinodesLanding'; import type { SxProps, Theme } from '@mui/material/styles'; import type { LinodeWithMaintenance } from 'src/utilities/linodes'; +const statusTooltipIcons = { + scheduled: , + active: , + pending: , +}; + +interface MaintenanceTextProps { + isOpened?: boolean; + maintenanceStartTime: string; +} + +const MaintenanceText = ({ + isOpened = false, + maintenanceStartTime, +}: MaintenanceTextProps) => { + return ( + <> + This Linode’s maintenance window {isOpened ? 'opened' : 'opens'} at{' '} + {maintenanceStartTime} + {!isOpened && <>. For more information, see your open support tickets}. + + ); +}; + interface Props extends LinodeWithMaintenance { handlers: LinodeHandlers; } @@ -61,22 +92,12 @@ export const LinodeRow = (props: Props) => { const isBareMetalInstance = linodeType?.class === 'metal'; - const loading = linodeInTransition(status, recentEvent); + const isTransitioning = linodeInTransition(status, recentEvent); const parsedMaintenanceStartTime = parseMaintenanceStartTime( - maintenance?.when + maintenance?.start_time || maintenance?.when ); - const MaintenanceText = () => { - return ( - <> - This Linode’s maintenance window opens at{' '} - {parsedMaintenanceStartTime}. For more information, see your{' '} - open support tickets. - - ); - }; - const iconStatus = getLinodeIconStatus(status); const [isHovered, setIsHovered] = React.useState(false); @@ -89,6 +110,12 @@ export const LinodeRow = (props: Props) => { setIsHovered(false); }, []); + const isPendingOrScheduled = + maintenance?.status === 'pending' || maintenance?.status === 'scheduled'; + + const isInProgress = + maintenance?.status === 'started' || maintenance?.status === 'in-progress'; + return ( { - {!maintenance ? ( - loading ? ( - <> - - - - - - ) : ( - <> - - {getFormattedStatus(status)} - - ) - ) : ( -
- Maintenance Scheduled - } - tooltipPosition="top" + + {!isTransitioning && getFormattedStatus(status)} + {isTransitioning && ( + + -
+ + )} + {isInProgress && ( + + } + tooltipPosition="top" + /> + )} + {isPendingOrScheduled && ( + + ) + } + tooltipPosition="top" + /> )}
diff --git a/packages/manager/src/features/Linodes/index.tsx b/packages/manager/src/features/Linodes/index.tsx index f496f96bd5e..b1158eff550 100644 --- a/packages/manager/src/features/Linodes/index.tsx +++ b/packages/manager/src/features/Linodes/index.tsx @@ -13,7 +13,7 @@ import { useInProgressEvents } from 'src/queries/events/events'; import { addMaintenanceToLinodes } from 'src/utilities/linodes'; import { storage } from 'src/utilities/storage'; -import { PENDING_MAINTENANCE_FILTER } from '../Account/Maintenance/utilities'; +import { PENDING_AND_IN_PROGRESS_MAINTENANCE_FILTER } from '../Account/Maintenance/utilities'; import { regionFilterOptions } from './LinodesLanding/RegionTypeFilter'; import { linodesInTransition } from './transitions'; @@ -54,7 +54,7 @@ export const LinodesRoutes = () => { export const LinodesLandingWrapper = React.memo(() => { const { data: accountMaintenanceData } = useAllAccountMaintenanceQuery( {}, - PENDING_MAINTENANCE_FILTER + PENDING_AND_IN_PROGRESS_MAINTENANCE_FILTER ); const flags = useFlags(); diff --git a/packages/manager/src/utilities/linodes.ts b/packages/manager/src/utilities/linodes.ts index 1a15856e892..24db282f610 100644 --- a/packages/manager/src/utilities/linodes.ts +++ b/packages/manager/src/utilities/linodes.ts @@ -5,12 +5,20 @@ import { useFlags } from 'src/hooks/useFlags'; import type { AccountMaintenance, Linode } from '@linode/api-v4'; -export interface Maintenance { +export interface LinodeMaintenance { + start_time: null | string; + status?: + | 'canceled' + | 'completed' + | 'in-progress' + | 'pending' + | 'scheduled' + | 'started'; when: null | string; } export interface LinodeWithMaintenance extends Linode { - maintenance?: Maintenance | null; + maintenance?: LinodeMaintenance | null; } export const addMaintenanceToLinodes = ( @@ -29,6 +37,8 @@ export const addMaintenanceToLinodes = ( ? { ...thisLinode, maintenance: { + start_time: foundMaintenance.start_time, + status: foundMaintenance.status, when: foundMaintenance.when, }, } diff --git a/packages/ui/.changeset/pr-12398-upcoming-features-1750738640469.md b/packages/ui/.changeset/pr-12398-upcoming-features-1750738640469.md new file mode 100644 index 00000000000..823466f4dad --- /dev/null +++ b/packages/ui/.changeset/pr-12398-upcoming-features-1750738640469.md @@ -0,0 +1,5 @@ +--- +"@linode/ui": Upcoming Features +--- + +Add new maintenance policy icons and update TooltipIcon tokens ([#12398](https://github.com/linode/manager/pull/12398)) diff --git a/packages/ui/src/assets/icons/calendar-schedule.svg b/packages/ui/src/assets/icons/calendar-schedule.svg new file mode 100644 index 00000000000..2a9adf39150 --- /dev/null +++ b/packages/ui/src/assets/icons/calendar-schedule.svg @@ -0,0 +1,3 @@ + + + diff --git a/packages/ui/src/assets/icons/calendar.svg b/packages/ui/src/assets/icons/calendar.svg new file mode 100644 index 00000000000..be88684a655 --- /dev/null +++ b/packages/ui/src/assets/icons/calendar.svg @@ -0,0 +1,3 @@ + + + diff --git a/packages/ui/src/assets/icons/index.ts b/packages/ui/src/assets/icons/index.ts index 11778dd4737..f8435916882 100644 --- a/packages/ui/src/assets/icons/index.ts +++ b/packages/ui/src/assets/icons/index.ts @@ -1,4 +1,6 @@ export { default as AlertIcon } from './alert.svg'; +export { default as CalendarScheduledIcon } from './calendar-schedule.svg'; +export { default as CalendarIcon } from './calendar.svg'; export { default as CheckIcon } from './check.svg'; export { default as CheckboxIcon } from './checkbox.svg'; export { default as CheckboxCheckedIcon } from './checkboxChecked.svg'; @@ -13,6 +15,7 @@ export { default as ErrorIcon } from './error.svg'; export { default as InfoOutlinedIcon } from './info-outlined.svg'; export { default as InfoIcon } from './info.svg'; export { default as LightBulbIcon } from './lightbulb.svg'; +export { default as LoadFailureIcon } from './load-failure.svg'; export { default as PendingIcon } from './pending.svg'; export { default as PlusSignIcon } from './plusSign.svg'; export { default as RadioIcon } from './radio.svg'; diff --git a/packages/ui/src/assets/icons/load-failure.svg b/packages/ui/src/assets/icons/load-failure.svg new file mode 100644 index 00000000000..ddb7481610c --- /dev/null +++ b/packages/ui/src/assets/icons/load-failure.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/packages/ui/src/components/TooltipIcon/TooltipIcon.tsx b/packages/ui/src/components/TooltipIcon/TooltipIcon.tsx index 2413be5a2a2..cc39bfeb0dc 100644 --- a/packages/ui/src/components/TooltipIcon/TooltipIcon.tsx +++ b/packages/ui/src/components/TooltipIcon/TooltipIcon.tsx @@ -20,6 +20,8 @@ export type TooltipIconStatus = | 'help' | 'info' | 'other' + | 'pending' + | 'scheduled' | 'success' | 'warning'; @@ -101,6 +103,7 @@ export const TooltipIcon = (props: TooltipIconProps) => { leaveDelay, status, sx, + className, sxTooltipIcon, text, tooltipAnalyticsEvent, @@ -134,7 +137,11 @@ export const TooltipIcon = (props: TooltipIconProps) => { switch (status) { case 'error': - renderIcon = ; + renderIcon = ( + + ); break; case 'help': renderIcon = ; @@ -146,10 +153,16 @@ export const TooltipIcon = (props: TooltipIconProps) => { renderIcon = icon ?? null; break; case 'success': - renderIcon = ; + renderIcon = ( + + ); break; case 'warning': - renderIcon = ; + renderIcon = ( + + ); break; default: renderIcon = null; @@ -170,6 +183,7 @@ export const TooltipIcon = (props: TooltipIconProps) => { width={width} > { // This prevents unwanted behavior when clicking a tooltip icon. diff --git a/packages/ui/src/foundations/themes/dark.ts b/packages/ui/src/foundations/themes/dark.ts index 3ca7ccd27d5..c4a9e6f7d2a 100644 --- a/packages/ui/src/foundations/themes/dark.ts +++ b/packages/ui/src/foundations/themes/dark.ts @@ -695,6 +695,23 @@ export const darkTheme: ThemeOptions = { '&:hover': { color: Content.Icon.Primary.Hover, }, + // "ui/src/components/TooltipIcon" Overrides + '&.ui-TooltipIcon': { + '&.ui-TooltipIcon-isActive': { + '&.MuiIconButton-root': { + color: Component.Button.Danger.Default.Background, + }, + '&.MuiIconButton-root:hover': { + color: Component.Button.Danger.Hover.Background, + }, + }, + '&.MuiIconButton-root': { + color: Component.Label.InfoIcon, + }, + '&.MuiIconButton-root:hover': { + color: Content.Icon.Primary.Hover, + }, + }, }, }, }, diff --git a/packages/ui/src/foundations/themes/light.ts b/packages/ui/src/foundations/themes/light.ts index e79e997b30f..dd8ee4348d5 100644 --- a/packages/ui/src/foundations/themes/light.ts +++ b/packages/ui/src/foundations/themes/light.ts @@ -976,6 +976,24 @@ export const lightTheme: ThemeOptions = { backgroundColor: 'transparent', color: Content.Icon.Primary.Hover, }, + // TooltipIcon Overrides + '&.ui-TooltipIcon': { + '&.ui-TooltipIcon-isActive': { + '&.MuiIconButton-root': { + color: Component.Button.Danger.Default.Background, + }, + '&.MuiIconButton-root:hover': { + color: Component.Button.Danger.Hover.Background, + }, + }, + '&.MuiIconButton-root': { + color: Component.Label.InfoIcon, + padding: `0 ${Spacing.S6}`, + }, + '&.MuiIconButton-root:hover': { + color: Content.Icon.Primary.Hover, + }, + }, }, }, },