github.com/thanos-io/thanos@v0.32.5/pkg/ui/react-app/src/pages/flags/Flags.tsx (about) 1 import React, { ChangeEvent, FC, useState } from 'react'; 2 import { RouteComponentProps } from '@reach/router'; 3 import { Input, InputGroup, Table } from 'reactstrap'; 4 import { withStatusIndicator } from '../../components/withStatusIndicator'; 5 import { useFetch } from '../../hooks/useFetch'; 6 import PathPrefixProps from '../../types/PathPrefixProps'; 7 import { faSort, faSortDown, faSortUp } from '@fortawesome/free-solid-svg-icons'; 8 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; 9 import { IconDefinition } from '@fortawesome/fontawesome-common-types'; 10 import sanitizeHTML from 'sanitize-html'; 11 import { Fuzzy, FuzzyResult } from '@nexucis/fuzzy'; 12 13 const fuz = new Fuzzy({ pre: '<strong>', post: '</strong>', shouldSort: true }); 14 const flagSeparator = '||'; 15 16 export interface FlagMap { 17 [key: string]: string; 18 } 19 20 interface FlagsProps { 21 data?: FlagMap; 22 } 23 24 const compareAlphaFn = 25 (keys: boolean, reverse: boolean) => 26 ([k1, v1]: [string, string], [k2, v2]: [string, string]): number => { 27 const a = keys ? k1 : v1; 28 const b = keys ? k2 : v2; 29 const reverser = reverse ? -1 : 1; 30 return reverser * a.localeCompare(b); 31 }; 32 33 const getSortIcon = (b: boolean | undefined): IconDefinition => { 34 if (b === undefined) { 35 return faSort; 36 } 37 if (b) { 38 return faSortDown; 39 } 40 return faSortUp; 41 }; 42 43 interface SortState { 44 name: string; 45 alpha: boolean; 46 focused: boolean; 47 } 48 49 export const FlagsContent: FC<FlagsProps> = ({ data = {} }) => { 50 const initialSearch = ''; 51 const [searchState, setSearchState] = useState(initialSearch); 52 const initialSort: SortState = { 53 name: 'Flag', 54 alpha: true, 55 focused: true, 56 }; 57 const [sortState, setSortState] = useState(initialSort); 58 const searchable = Object.entries(data) 59 .sort(compareAlphaFn(sortState.name === 'Flag', !sortState.alpha)) 60 .map(([flag, value]) => `--${flag}${flagSeparator}${value}`); 61 let filtered = searchable; 62 if (searchState.length > 0) { 63 filtered = fuz.filter(searchState, searchable).map((value: FuzzyResult) => value.rendered); 64 } 65 return ( 66 <> 67 <h2>Command-Line Flags</h2> 68 <InputGroup> 69 <Input 70 autoFocus 71 placeholder="Filter by flag name or value..." 72 className="my-3" 73 value={searchState} 74 onChange={({ target }: ChangeEvent<HTMLInputElement>): void => { 75 setSearchState(target.value); 76 }} 77 /> 78 </InputGroup> 79 <Table bordered size="sm" striped hover> 80 <thead> 81 <tr> 82 {['Flag', 'Value'].map((col: string) => ( 83 <td 84 key={col} 85 className={`px-4 ${col}`} 86 style={{ width: '50%' }} 87 onClick={(): void => 88 setSortState({ 89 name: col, 90 focused: true, 91 alpha: sortState.name === col ? !sortState.alpha : true, 92 }) 93 } 94 > 95 <span className="mr-2">{col}</span> 96 <FontAwesomeIcon icon={getSortIcon(sortState.name !== col ? undefined : sortState.alpha)} /> 97 </td> 98 ))} 99 </tr> 100 </thead> 101 <tbody> 102 {filtered.map((result: string) => { 103 const [flagMatchStr, valueMatchStr] = result.split(flagSeparator); 104 const sanitizeOpts = { allowedTags: ['strong'] }; 105 return ( 106 <tr key={flagMatchStr}> 107 <td className="flag-item"> 108 <span dangerouslySetInnerHTML={{ __html: sanitizeHTML(flagMatchStr, sanitizeOpts) }} /> 109 </td> 110 <td className="flag-value"> 111 <span dangerouslySetInnerHTML={{ __html: sanitizeHTML(valueMatchStr, sanitizeOpts) }} /> 112 </td> 113 </tr> 114 ); 115 })} 116 </tbody> 117 </Table> 118 </> 119 ); 120 }; 121 const FlagsWithStatusIndicator = withStatusIndicator(FlagsContent); 122 123 FlagsContent.displayName = 'Flags'; 124 125 const Flags: FC<RouteComponentProps & PathPrefixProps> = ({ pathPrefix = '' }) => { 126 const { response, error, isLoading } = useFetch<FlagMap>(`${pathPrefix}/api/v1/status/flags`); 127 return <FlagsWithStatusIndicator data={response.data} error={error} isLoading={isLoading} />; 128 }; 129 130 export default Flags;