diff --git a/src/component-library/features/search/DIDSearchPanel.tsx b/src/component-library/features/search/DIDSearchPanel.tsx index dc33b1af..aaf39811 100644 --- a/src/component-library/features/search/DIDSearchPanel.tsx +++ b/src/component-library/features/search/DIDSearchPanel.tsx @@ -242,13 +242,26 @@ export const DIDSearchPanel = (props: SearchPanelProps) => { const onScopeArrowDown = (event: React.KeyboardEvent) => { if (event.key === 'ArrowRight') { - nameInputRef.current?.focus(); + const input = event.currentTarget; + const cursorPosition = input.selectionStart; + const textLength = input.value.length; + + // Only switch to name input if cursor is at the end + if (cursorPosition === textLength) { + nameInputRef.current?.focus(); + } } }; const onNameArrowDown = (event: React.KeyboardEvent) => { if (event.key === 'ArrowLeft') { - scopeInputRef.current?.focus(); + const input = event.currentTarget; + const cursorPosition = input.selectionStart; + + // Only switch to scope input if cursor is at the beginning + if (cursorPosition === 0) { + scopeInputRef.current?.focus(); + } } }; diff --git a/src/component-library/pages/DID/details/views/DetailsDIDRules.tsx b/src/component-library/pages/DID/details/views/DetailsDIDRules.tsx index ef12cf73..26601157 100644 --- a/src/component-library/pages/DID/details/views/DetailsDIDRules.tsx +++ b/src/component-library/pages/DID/details/views/DetailsDIDRules.tsx @@ -18,7 +18,7 @@ import useTableStreaming from '@/lib/infrastructure/hooks/useTableStreaming'; import { formatDate, formatSeconds } from '@/component-library/features/utils/text-formatters'; import { RuleStateBadge } from '@/component-library/features/badges/Rule/RuleStateBadge'; import { NullBadge } from '@/component-library/features/badges/NullBadge'; -import { ruleActivityComparator } from '@/lib/core/utils/rule-sorting-utils'; +import { ruleActivityComparator, remainingLifetimeComparator, ruleStateComparator } from '@/lib/core/utils/rule-sorting-utils'; type DetailsDIDRulesTableProps = { streamingHook: UseStreamReader; @@ -76,6 +76,8 @@ export const DetailsDIDRulesTable = (props: DetailsDIDRulesTableProps) => { }, filter: true, filterParams: buildDiscreteFilterParams(Object.values(RuleStateDisplayNames), Object.values(RuleState)), + sortable: true, + comparator: ruleStateComparator, }, // TODO: minified header with a tooltip { @@ -125,16 +127,18 @@ export const DetailsDIDRulesTable = (props: DetailsDIDRulesTableProps) => { minWidth: 120, width: 140, cellRenderer: NullableRemainingLifetime, + sortable: true, + comparator: remainingLifetimeComparator, }, ]); const onGridReady = (event: GridReadyEvent) => { props.onGridReady(event); - // Apply default sort to prioritize stuck rules + // Apply default sort to prioritize error/stuck rules event.api.applyColumnState({ state: [ { - colId: 'locks_stuck_cnt', + colId: 'state', sort: 'desc', }, ], diff --git a/src/component-library/pages/Rule/details/DetailsRuleLocks.tsx b/src/component-library/pages/Rule/details/DetailsRuleLocks.tsx index 1bd509d9..a7c51816 100644 --- a/src/component-library/pages/Rule/details/DetailsRuleLocks.tsx +++ b/src/component-library/pages/Rule/details/DetailsRuleLocks.tsx @@ -16,6 +16,7 @@ import { FTSLinkViewModel } from '@/lib/infrastructure/data/view-model/request'; import { useToast } from '@/lib/infrastructure/hooks/useToast'; import { LoadingSpinner } from '@/component-library/atoms/loading/LoadingSpinner'; import { HiExternalLink } from 'react-icons/hi'; +import { lockStateComparator } from '@/lib/core/utils/rule-sorting-utils'; type DetailsRuleLocksTableProps = { streamingHook: UseStreamReader; @@ -150,6 +151,8 @@ const DetailsRuleLocksTable = (props: DetailsRuleLocksTableProps) => { }, filter: true, filterParams: buildDiscreteFilterParams(Object.values(LockStateDisplayNames), Object.values(LockState)), + sortable: true, + comparator: lockStateComparator, }, { headerName: 'FTS Monitoring', @@ -159,7 +162,20 @@ const DetailsRuleLocksTable = (props: DetailsRuleLocksTableProps) => { }, ]); - return ; + const onGridReady = (event: GridReadyEvent) => { + props.onGridReady(event); + // Apply default sort to prioritize error/stuck locks + event.api.applyColumnState({ + state: [ + { + colId: 'state', + sort: 'desc', + }, + ], + }); + }; + + return ; }; export const DetailsRuleLocks = ({ id }: { id: string }) => { diff --git a/src/component-library/pages/Rule/list/ListRuleTable.tsx b/src/component-library/pages/Rule/list/ListRuleTable.tsx index 2280499e..bc33f517 100644 --- a/src/component-library/pages/Rule/list/ListRuleTable.tsx +++ b/src/component-library/pages/Rule/list/ListRuleTable.tsx @@ -16,7 +16,7 @@ import { formatDate, formatSeconds } from '@/component-library/features/utils/te import { RuleStateBadge } from '@/component-library/features/badges/Rule/RuleStateBadge'; import { RuleState } from '@/lib/core/entity/rucio'; import { NullBadge } from '@/component-library/features/badges/NullBadge'; -import { ruleActivityComparator, remainingLifetimeComparator } from '@/lib/core/utils/rule-sorting-utils'; +import { ruleActivityComparator, remainingLifetimeComparator, ruleStateComparator } from '@/lib/core/utils/rule-sorting-utils'; type ListRuleTableProps = { streamingHook: UseStreamReader; @@ -96,6 +96,8 @@ export const ListRuleTable = (props: ListRuleTableProps) => { }, filter: true, filterParams: buildDiscreteFilterParams(Object.values(RuleStateDisplayNames), Object.values(RuleState)), + sortable: true, + comparator: ruleStateComparator, }, // TODO: minified header with a tooltip { @@ -153,11 +155,11 @@ export const ListRuleTable = (props: ListRuleTableProps) => { const onGridReady = (event: GridReadyEvent) => { props.onGridReady(event); - // Apply default sort to prioritize stuck rules + // Apply default sort to prioritize error/stuck rules event.api.applyColumnState({ state: [ { - colId: 'locks_stuck_cnt', + colId: 'state', sort: 'desc', }, ], diff --git a/src/lib/core/utils/rule-sorting-utils.ts b/src/lib/core/utils/rule-sorting-utils.ts index b85b6984..a1c70e3d 100644 --- a/src/lib/core/utils/rule-sorting-utils.ts +++ b/src/lib/core/utils/rule-sorting-utils.ts @@ -78,3 +78,57 @@ export function remainingLifetimeComparator(valueA: number | null | undefined, v // Numeric comparison (values are in seconds) return valueA - valueB; } + +/** + * Get priority score for lock states + * Higher scores indicate higher priority (more urgent states) + */ +function getLockStatePriority(state: string): number { + const stateLower = state.toLowerCase(); + + if (stateLower.includes('error')) return 4; + if (stateLower === 'stuck') return 3; + if (stateLower === 'replicating') return 2; + if (stateLower === 'ok') return 1; + return 0; // unknown or other states +} + +/** + * Comparator function for ag-Grid that sorts lock states by priority + * Order: ERROR > STUCK > REPLICATING > OK > UNKNOWN + */ +export function lockStateComparator(valueA: string, valueB: string): number { + const priorityA = getLockStatePriority(valueA); + const priorityB = getLockStatePriority(valueB); + + // Higher priority first + return priorityB - priorityA; +} + +/** + * Get priority score for rule states + * Higher scores indicate higher priority (more urgent states) + */ +function getRuleStatePriority(state: string): number { + const stateLower = state.toLowerCase(); + + if (stateLower === 'stuck') return 6; + if (stateLower === 'suspended') return 5; + if (stateLower === 'replicating') return 4; + if (stateLower === 'waiting_approval') return 3; + if (stateLower === 'inject') return 2; + if (stateLower === 'ok') return 1; + return 0; // unknown or other states +} + +/** + * Comparator function for ag-Grid that sorts rule states by priority + * Order: STUCK > SUSPENDED > REPLICATING > WAITING_APPROVAL > INJECT > OK > UNKNOWN + */ +export function ruleStateComparator(valueA: string, valueB: string): number { + const priorityA = getRuleStatePriority(valueA); + const priorityB = getRuleStatePriority(valueB); + + // Higher priority first + return priorityB - priorityA; +}