-
Notifications
You must be signed in to change notification settings - Fork 2.1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
ui: Add Stores page to React UI #2754
Changes from 3 commits
63e7e09
78b600e
5273fa5
16a9ce3
301f8d7
623a8e1
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1,3 @@ | ||
export {}; | ||
import Stores from './stores/Stores'; | ||
|
||
export { Stores }; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
import React, { FC } from 'react'; | ||
import { Badge, ListGroup, ListGroupItem } from 'reactstrap'; | ||
import { Labels } from './store'; | ||
|
||
export type StoreLabelsProps = { labelSet: Labels[] }; | ||
|
||
export const StoreLabels: FC<StoreLabelsProps> = ({ labelSet }) => { | ||
return ( | ||
<ListGroup> | ||
{labelSet.map(({ labels }, idx) => ( | ||
<ListGroupItem key={idx}> | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. One thing, for consistency let's try to use the same variable named for common things like indexes in loops. Not sure about the rest of the tsx codebase in the repo but let's just pick either i or idx There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We haven't found any other loops that use some other name and other code uses |
||
{labels.map(label => ( | ||
<Badge key={label.name} color="primary" style={{ margin: '0px 5px' }}>{`${label.name}="${label.value}"`}</Badge> | ||
))} | ||
</ListGroupItem> | ||
))} | ||
</ListGroup> | ||
); | ||
}; | ||
|
||
export default StoreLabels; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,80 @@ | ||
import React, { FC } from 'react'; | ||
import { Container, Collapse, Table, Badge } from 'reactstrap'; | ||
import { now } from 'moment'; | ||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; | ||
import { faInfinity } from '@fortawesome/free-solid-svg-icons'; | ||
import { ToggleMoreLess } from '../../../components/ToggleMoreLess'; | ||
import { useLocalStorage } from '../../../hooks/useLocalStorage'; | ||
import { getColor } from '../../../pages/targets/target'; | ||
import { formatRelative, formatTime, parseTime } from '../../../utils'; | ||
import { Store } from './store'; | ||
import StoreLabels from './StoreLabels'; | ||
|
||
export type StorePoolPanelProps = { title: string; storePool: Store[] }; | ||
|
||
export const columns = [ | ||
'Endpoint', | ||
'Status', | ||
'Announced LabelSets', | ||
'Min Time', | ||
'Max Time', | ||
'Last Successful Health Check', | ||
'Last Message', | ||
]; | ||
|
||
const MAX_TIME = 9223372036854775807; | ||
|
||
export const StorePoolPanel: FC<StorePoolPanelProps> = ({ title, storePool }) => { | ||
const [{ expanded }, setOptions] = useLocalStorage(`store-pool-${title}-expanded`, { expanded: true }); | ||
|
||
return ( | ||
<Container fluid> | ||
<ToggleMoreLess event={(): void => setOptions({ expanded: !expanded })} showMore={expanded}> | ||
<span style={{ textTransform: 'capitalize' }}>{title}</span> | ||
</ToggleMoreLess> | ||
<Collapse isOpen={expanded}> | ||
<Table size="sm" bordered hover> | ||
<thead> | ||
<tr key="header"> | ||
{columns.map(column => ( | ||
<th key={column}>{column}</th> | ||
))} | ||
</tr> | ||
</thead> | ||
<tbody> | ||
{storePool.map((store: Store) => { | ||
const { name, minTime, maxTime, labelSets, lastCheck, lastError } = store; | ||
const health = lastError ? 'down' : 'up'; | ||
const color = getColor(health); | ||
|
||
return ( | ||
<tr key={name}> | ||
<td>{name}</td> | ||
<td> | ||
<Badge color={color}>{health.toUpperCase()}</Badge> | ||
</td> | ||
<td> | ||
<StoreLabels labelSet={labelSets} /> | ||
</td> | ||
<td>{minTime >= MAX_TIME ? <FontAwesomeIcon icon={faInfinity} /> : formatTime(minTime)}</td> | ||
<td>{maxTime >= MAX_TIME ? <FontAwesomeIcon icon={faInfinity} /> : formatTime(maxTime)}</td> | ||
<td> | ||
{parseTime(lastCheck) >= MAX_TIME ? ( | ||
GiedriusS marked this conversation as resolved.
Show resolved
Hide resolved
|
||
<FontAwesomeIcon icon={faInfinity} /> | ||
) : ( | ||
formatRelative(lastCheck, now()) | ||
)}{' '} | ||
ago | ||
</td> | ||
<td>{lastError ? <Badge color={color}>{lastError}</Badge> : null}</td> | ||
</tr> | ||
); | ||
})} | ||
</tbody> | ||
</Table> | ||
</Collapse> | ||
</Container> | ||
); | ||
}; | ||
|
||
export default StorePoolPanel; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
import React, { FC } from 'react'; | ||
import { RouteComponentProps } from '@reach/router'; | ||
import { withStatusIndicator } from '../../../components/withStatusIndicator'; | ||
import { useFetch } from '../../../hooks/useFetch'; | ||
import { Store } from './store'; | ||
import { StorePoolPanel } from './StorePoolPanel'; | ||
|
||
interface StoreListProps { | ||
[storeType: string]: Store[]; | ||
} | ||
|
||
export const StoreContent: FC<{ data: StoreListProps }> = ({ data }) => { | ||
return ( | ||
<> | ||
{Object.keys(data).map<JSX.Element>(storeGroup => ( | ||
<StorePoolPanel key={storeGroup} title={storeGroup} storePool={data[storeGroup]} /> | ||
))} | ||
</> | ||
); | ||
}; | ||
|
||
const StoresWithStatusIndicator = withStatusIndicator(StoreContent); | ||
|
||
export const Stores: FC<RouteComponentProps> = () => { | ||
const { response, error, isLoading } = useFetch<StoreListProps>(`/api/v1/stores`); | ||
const { status: responseStatus } = response; | ||
const badResponse = responseStatus !== 'success' && responseStatus !== 'start fetching'; | ||
|
||
return ( | ||
<StoresWithStatusIndicator | ||
data={response.data} | ||
error={badResponse ? new Error(responseStatus) : error} | ||
isLoading={isLoading} | ||
/> | ||
); | ||
}; | ||
|
||
export default Stores; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
export interface Label { | ||
name: string; | ||
value: string; | ||
} | ||
|
||
export interface Labels { | ||
labels: Label[]; | ||
} | ||
|
||
export interface Store { | ||
name: string; | ||
minTime: number; | ||
maxTime: number; | ||
lastError: string | null; | ||
lastCheck: string; | ||
labelSets: Labels[]; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -34,6 +34,7 @@ var ( | |
"/targets", | ||
"/tsdb-status", | ||
"/version", | ||
"/stores", | ||
} | ||
) | ||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can't wait till we can get rid of the"new" prefix :)