github.com/thanos-io/thanos@v0.32.5/pkg/ui/react-app/src/thanos/pages/blocks/BlockDetails.tsx (about) 1 import React, { FC, useState } from 'react'; 2 import { Block } from './block'; 3 import styles from './blocks.module.css'; 4 import moment from 'moment'; 5 import { Button, Modal, ModalBody, Form, Input, ModalHeader, ModalFooter } from 'reactstrap'; 6 import { download } from './helpers'; 7 8 export interface BlockDetailsProps { 9 block: Block | undefined; 10 selectBlock: React.Dispatch<React.SetStateAction<Block | undefined>>; 11 } 12 13 export const BlockDetails: FC<BlockDetailsProps> = ({ block, selectBlock }) => { 14 const [modalAction, setModalAction] = useState<string>(''); 15 const [detailValue, setDetailValue] = useState<string | null>(null); 16 17 const submitMarkBlock = async (action: string, ulid: string, detail: string | null) => { 18 try { 19 const body = detail 20 ? new URLSearchParams({ 21 id: ulid, 22 action, 23 detail, 24 }) 25 : new URLSearchParams({ 26 id: ulid, 27 action, 28 }); 29 30 const response = await fetch('/api/v1/blocks/mark', { 31 method: 'POST', 32 body, 33 }); 34 35 if (!response.ok) { 36 throw new Error(response.statusText); 37 } 38 } finally { 39 setModalAction(''); 40 } 41 }; 42 43 return ( 44 <div className={`${styles.blockDetails} ${block && styles.open}`}> 45 {block && ( 46 <> 47 <div className={styles.detailsTop}> 48 <span className={styles.header} data-testid="ulid"> 49 {block.ulid} 50 </span> 51 <button className={styles.closeBtn} onClick={(): void => selectBlock(undefined)}> 52 × 53 </button> 54 </div> 55 <hr /> 56 <div data-testid="start-time"> 57 <b>Start Time:</b> <span>{moment.unix(block.minTime / 1000).format('LLL')}</span> 58 </div> 59 <div data-testid="end-time"> 60 <b>End Time:</b> <span>{moment.unix(block.maxTime / 1000).format('LLL')}</span> 61 </div> 62 <div data-testid="duration"> 63 <b>Duration:</b> <span>{moment.duration(block.maxTime - block.minTime, 'ms').humanize()}</span> 64 </div> 65 <hr /> 66 <div data-testid="series"> 67 <b>Series:</b> <span>{block.stats.numSeries}</span> 68 </div> 69 <div data-testid="samples"> 70 <b>Samples:</b> <span>{block.stats.numSamples}</span> 71 </div> 72 <div data-testid="chunks"> 73 <b>Chunks:</b> <span>{block.stats.numChunks}</span> 74 </div> 75 <hr /> 76 <div data-testid="resolution"> 77 <b>Resolution:</b> <span>{block.thanos.downsample.resolution}</span> 78 </div> 79 <div data-testid="level"> 80 <b>Level:</b> <span>{block.compaction.level}</span> 81 </div> 82 <div data-testid="source"> 83 <b>Source:</b> <span>{block.thanos.source}</span> 84 </div> 85 <hr /> 86 <div data-testid="labels"> 87 <b>Labels:</b> 88 <ul> 89 {Object.entries(block.thanos.labels).map(([key, value]) => ( 90 <li key={key}> 91 <b>{key}: </b> 92 {value} 93 </li> 94 ))} 95 </ul> 96 </div> 97 <hr /> 98 <div data-testid="download"> 99 <a href={download(block)} download="meta.json"> 100 <Button>Download meta.json</Button> 101 </a> 102 </div> 103 <div style={{ marginTop: '12px' }}> 104 <Button 105 onClick={() => { 106 setModalAction('DELETION'); 107 setDetailValue(''); 108 }} 109 > 110 Mark Deletion 111 </Button> 112 </div> 113 <div style={{ marginTop: '12px' }}> 114 <Button 115 onClick={() => { 116 setModalAction('NO_COMPACTION'); 117 setDetailValue(''); 118 }} 119 > 120 Mark No Compaction 121 </Button> 122 </div> 123 <Modal isOpen={!!modalAction}> 124 <ModalBody> 125 <ModalHeader toggle={() => setModalAction('')}> 126 Mark {modalAction === 'DELETION' ? 'Deletion' : 'No Compaction'} Detail (Optional) 127 </ModalHeader> 128 <Form 129 onSubmit={(e) => { 130 e.preventDefault(); 131 submitMarkBlock(modalAction, block.ulid, detailValue); 132 }} 133 > 134 <Input 135 placeholder="Reason for marking block..." 136 style={{ marginBottom: '16px', marginTop: '16px' }} 137 onChange={(e) => setDetailValue(e.target.value)} 138 /> 139 <ModalFooter> 140 <Button color="primary" type="submit"> 141 Submit 142 </Button> 143 </ModalFooter> 144 </Form> 145 </ModalBody> 146 </Modal> 147 </> 148 )} 149 </div> 150 ); 151 };