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;