Skip to content

Commit 17e1da1

Browse files
committed
UI: add dark theme
Signed-off-by: Aman Dwivedi <[email protected]>
1 parent eacc686 commit 17e1da1

28 files changed

+939
-235
lines changed

CHANGELOG.md

+3-1
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,9 @@ We use _breaking :warning:_ to mark changes that are not backward compatible (re
2424
### Fixed
2525
-
2626
### Changed
27-
-
27+
28+
- [#4249](https://github.com/thanos-io/thanos/pull/4249) UI: add dark theme
29+
2830
### Removed
2931
-
3032

pkg/ui/bindata.go

+126-126
Large diffs are not rendered by default.

pkg/ui/react-app/package.json

+4-1
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
"@codemirror/search": "^0.18.2",
1616
"@codemirror/state": "^0.18.2",
1717
"@codemirror/view": "^0.18.3",
18+
"@forevolve/bootstrap-dark": "^1.0.0",
1819
"@fortawesome/fontawesome-svg-core": "^1.2.34",
1920
"@fortawesome/free-solid-svg-icons": "^5.15.2",
2021
"@fortawesome/react-fontawesome": "^0.1.14",
@@ -44,9 +45,11 @@
4445
"react-test-renderer": "^16.14.0",
4546
"reactstrap": "^8.9.0",
4647
"sanitize-html": "^2.3.2",
48+
"sass": "^1.32.13",
4749
"tempusdominus-bootstrap-4": "^5.39.0",
4850
"tempusdominus-core": "^5.19.0",
4951
"typescript": "3.9.9",
52+
"use-media": "^1.4.0",
5053
"use-query-params": "^1.1.9"
5154
},
5255
"scripts": {
@@ -79,12 +82,12 @@
7982
"@types/moment-timezone": "^0.5.30",
8083
"@types/node": "^14.14.30",
8184
"@types/reach__router": "^1.3.7",
82-
"@types/reactstrap": "^8.7.2",
8385
"@types/react": "^17.0.2",
8486
"@types/react-copy-to-clipboard": "^5.0.0",
8587
"@types/react-dom": "^17.0.1",
8688
"@types/react-resize-detector": "^4.0.2",
8789
"@types/react-select": "^4.0.13",
90+
"@types/reactstrap": "^8.7.2",
8891
"@types/sanitize-html": "^1.27.1",
8992
"@types/sinon": "^9.0.10",
9093
"@typescript-eslint/eslint-plugin": "^4.15.1",

pkg/ui/react-app/public/index.html

+1-1
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@
3636
-->
3737
<title>Thanos | Highly available Prometheus setup</title>
3838
</head>
39-
<body>
39+
<body class="bootstrap">
4040
<noscript>You need to enable JavaScript to run this app.</noscript>
4141
<div id="root"></div>
4242
<!--

pkg/ui/react-app/src/App.tsx

+47-29
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,16 @@ import React, { FC } from 'react';
22
import { Container } from 'reactstrap';
33
import { Router, Redirect, globalHistory } from '@reach/router';
44
import { QueryParamProvider } from 'use-query-params';
5+
import useMedia from 'use-media';
56

67
import { Alerts, Config, Flags, Rules, ServiceDiscovery, Status, Targets, TSDBStatus, PanelList, NotFound } from './pages';
78
import PathPrefixProps from './types/PathPrefixProps';
89
import ThanosComponentProps from './thanos/types/ThanosComponentProps';
910
import Navigation from './thanos/Navbar';
1011
import { Stores, ErrorBoundary, Blocks } from './thanos/pages';
11-
12-
import './App.css';
12+
import { ThemeContext, themeName, themeSetting } from './contexts/ThemeContext';
13+
import { Theme, themeLocalStorageKey } from './Theme';
14+
import { useLocalStorage } from './hooks/useLocalStorage';
1315

1416
const defaultRouteConfig: { [component: string]: string } = {
1517
query: '/graph',
@@ -20,35 +22,51 @@ const defaultRouteConfig: { [component: string]: string } = {
2022
};
2123

2224
const App: FC<PathPrefixProps & ThanosComponentProps> = ({ pathPrefix, thanosComponent }) => {
25+
const [userTheme, setUserTheme] = useLocalStorage<themeSetting>(themeLocalStorageKey, 'auto');
26+
const browserHasThemes = useMedia('(prefers-color-scheme)');
27+
const browserWantsDarkTheme = useMedia('(prefers-color-scheme: dark)');
28+
29+
let theme: themeName;
30+
if (userTheme !== 'auto') {
31+
theme = userTheme;
32+
} else {
33+
theme = browserHasThemes ? (browserWantsDarkTheme ? 'dark' : 'light') : 'light';
34+
}
35+
2336
return (
24-
<ErrorBoundary>
25-
<Navigation
26-
pathPrefix={pathPrefix}
27-
thanosComponent={thanosComponent}
28-
defaultRoute={defaultRouteConfig[thanosComponent]}
29-
/>
30-
<Container fluid style={{ paddingTop: 70 }}>
31-
<QueryParamProvider reachHistory={globalHistory}>
32-
<Router basepath={`${pathPrefix}`}>
33-
<Redirect from="/" to={`${pathPrefix}${defaultRouteConfig[thanosComponent]}`} />
37+
<ThemeContext.Provider
38+
value={{ theme: theme, userPreference: userTheme, setTheme: (t: themeSetting) => setUserTheme(t) }}
39+
>
40+
<Theme />
41+
<ErrorBoundary>
42+
<Navigation
43+
pathPrefix={pathPrefix}
44+
thanosComponent={thanosComponent}
45+
defaultRoute={defaultRouteConfig[thanosComponent]}
46+
/>
47+
<Container fluid style={{ paddingTop: 70 }}>
48+
<QueryParamProvider reachHistory={globalHistory}>
49+
<Router basepath={`${pathPrefix}`}>
50+
<Redirect from="/" to={`${pathPrefix}${defaultRouteConfig[thanosComponent]}`} />
3451

35-
<PanelList path="/graph" pathPrefix={pathPrefix} />
36-
<Alerts path="/alerts" pathPrefix={pathPrefix} />
37-
<Config path="/config" pathPrefix={pathPrefix} />
38-
<Flags path="/flags" pathPrefix={pathPrefix} />
39-
<Rules path="/rules" pathPrefix={pathPrefix} />
40-
<ServiceDiscovery path="/service-discovery" pathPrefix={pathPrefix} />
41-
<Status path="/status" pathPrefix={pathPrefix} />
42-
<TSDBStatus path="/tsdb-status" pathPrefix={pathPrefix} />
43-
<Targets path="/targets" pathPrefix={pathPrefix} />
44-
<Stores path="/stores" pathPrefix={pathPrefix} />
45-
<Blocks path="/blocks" pathPrefix={pathPrefix} />
46-
<Blocks path="/loaded" pathPrefix={pathPrefix} view="loaded" />
47-
<NotFound pathPrefix={pathPrefix} default defaultRoute={defaultRouteConfig[thanosComponent]} />
48-
</Router>
49-
</QueryParamProvider>
50-
</Container>
51-
</ErrorBoundary>
52+
<PanelList path="/graph" pathPrefix={pathPrefix} />
53+
<Alerts path="/alerts" pathPrefix={pathPrefix} />
54+
<Config path="/config" pathPrefix={pathPrefix} />
55+
<Flags path="/flags" pathPrefix={pathPrefix} />
56+
<Rules path="/rules" pathPrefix={pathPrefix} />
57+
<ServiceDiscovery path="/service-discovery" pathPrefix={pathPrefix} />
58+
<Status path="/status" pathPrefix={pathPrefix} />
59+
<TSDBStatus path="/tsdb-status" pathPrefix={pathPrefix} />
60+
<Targets path="/targets" pathPrefix={pathPrefix} />
61+
<Stores path="/stores" pathPrefix={pathPrefix} />
62+
<Blocks path="/blocks" pathPrefix={pathPrefix} />
63+
<Blocks path="/loaded" pathPrefix={pathPrefix} view="loaded" />
64+
<NotFound pathPrefix={pathPrefix} default defaultRoute={defaultRouteConfig[thanosComponent]} />
65+
</Router>
66+
</QueryParamProvider>
67+
</Container>
68+
</ErrorBoundary>
69+
</ThemeContext.Provider>
5270
);
5371
};
5472

pkg/ui/react-app/src/Navbar.tsx

+3-1
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import {
1313
DropdownItem,
1414
} from 'reactstrap';
1515
import PathPrefixProps from './types/PathPrefixProps';
16+
import { ThemeToggle } from './Theme';
1617

1718
interface NavbarProps {
1819
consolesLink: string | null;
@@ -23,7 +24,7 @@ const Navigation: FC<PathPrefixProps & NavbarProps> = ({ pathPrefix, consolesLin
2324
const toggle = () => setIsOpen(!isOpen);
2425
return (
2526
<Navbar className="mb-3" dark color="dark" expand="md" fixed="top">
26-
<NavbarToggler onClick={toggle} />
27+
<NavbarToggler onClick={toggle} className="mr-2" />
2728
<Link className="pt-0 navbar-brand" to={`${pathPrefix}/graph`}>
2829
Prometheus
2930
</Link>
@@ -80,6 +81,7 @@ const Navigation: FC<PathPrefixProps & NavbarProps> = ({ pathPrefix, consolesLin
8081
</NavItem>
8182
</Nav>
8283
</Collapse>
84+
<ThemeToggle />
8385
</Navbar>
8486
);
8587
};

pkg/ui/react-app/src/Theme.tsx

+48
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import React, { FC, useEffect } from 'react';
2+
import { Form, Button, ButtonGroup } from 'reactstrap';
3+
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
4+
import { faMoon, faSun, faAdjust } from '@fortawesome/free-solid-svg-icons';
5+
import { useTheme } from './contexts/ThemeContext';
6+
7+
export const themeLocalStorageKey = 'user-prefers-color-scheme';
8+
9+
export const Theme: FC = () => {
10+
const { theme } = useTheme();
11+
12+
useEffect(() => {
13+
document.body.classList.toggle('bootstrap-dark', theme === 'dark');
14+
document.body.classList.toggle('bootstrap', theme === 'light');
15+
}, [theme]);
16+
17+
return null;
18+
};
19+
20+
export const ThemeToggle: FC = () => {
21+
const { userPreference, setTheme } = useTheme();
22+
23+
return (
24+
<Form className="ml-auto" inline>
25+
<ButtonGroup size="sm">
26+
<Button
27+
color="secondary"
28+
title="Use light theme"
29+
active={userPreference === 'light'}
30+
onClick={() => setTheme('light')}
31+
>
32+
<FontAwesomeIcon icon={faSun} className={userPreference === 'light' ? 'text-white' : 'text-dark'} />
33+
</Button>
34+
<Button color="secondary" title="Use dark theme" active={userPreference === 'dark'} onClick={() => setTheme('dark')}>
35+
<FontAwesomeIcon icon={faMoon} className={userPreference === 'dark' ? 'text-white' : 'text-dark'} />
36+
</Button>
37+
<Button
38+
color="secondary"
39+
title="Use browser-preferred theme"
40+
active={userPreference === 'auto'}
41+
onClick={() => setTheme('auto')}
42+
>
43+
<FontAwesomeIcon icon={faAdjust} className={userPreference === 'auto' ? 'text-white' : 'text-dark'} />
44+
</Button>
45+
</ButtonGroup>
46+
</Form>
47+
);
48+
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import React from 'react';
2+
3+
export type themeName = 'light' | 'dark';
4+
export type themeSetting = themeName | 'auto';
5+
6+
export interface ThemeCtx {
7+
theme: themeName;
8+
userPreference: themeSetting;
9+
setTheme: (t: themeSetting) => void;
10+
}
11+
12+
// defaults, will be overriden in App.tsx
13+
export const ThemeContext = React.createContext<ThemeCtx>({
14+
theme: 'light',
15+
userPreference: 'auto',
16+
// eslint-disable-next-line @typescript-eslint/no-empty-function
17+
setTheme: (s: themeSetting) => {},
18+
});
19+
20+
export const useTheme = () => {
21+
return React.useContext(ThemeContext);
22+
};

pkg/ui/react-app/src/index.tsx

+3-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@ import './globals';
22
import React from 'react';
33
import ReactDOM from 'react-dom';
44
import App from './App';
5-
import 'bootstrap/dist/css/bootstrap.min.css';
5+
import './themes/app.scss';
6+
import './themes/light.scss';
7+
import './themes/dark.scss';
68
import './fonts/codicon.ttf';
79
import { isPresent } from './utils';
810

pkg/ui/react-app/src/pages/alerts/CollapsibleAlertPanel.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ const CollapsibleAlertPanel: FC<CollapsibleAlertPanelProps> = ({ rule, showAnnot
2727
<strong>{rule.name}</strong> ({`${rule.alerts.length} active`})
2828
</Alert>
2929
<Collapse isOpen={open} className="mb-2">
30-
<pre style={{ background: '#f5f5f5', padding: 15 }}>
30+
<pre className="alert-cell">
3131
<code>
3232
<div>
3333
name: <a href={createExternalExpressionLink(`ALERTS{alertname="${rule.name}"}`)}>{rule.name}</a>

pkg/ui/react-app/src/pages/config/Config.css

-10
This file was deleted.

pkg/ui/react-app/src/pages/config/Config.tsx

-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import { Button } from 'reactstrap';
44
import CopyToClipboard from 'react-copy-to-clipboard';
55
import PathPrefixProps from '../../types/PathPrefixProps';
66

7-
import './Config.css';
87
import { withStatusIndicator } from '../../components/withStatusIndicator';
98
import { useFetch } from '../../hooks/useFetch';
109

pkg/ui/react-app/src/pages/graph/CMExpressionInput.tsx

+10-4
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,12 @@ import { commentKeymap } from '@codemirror/comment';
1212
import { lintKeymap } from '@codemirror/lint';
1313
import { PromQLExtension, CompleteStrategy } from 'codemirror-promql';
1414
import { autocompletion, completionKeymap, CompletionContext, CompletionResult } from '@codemirror/autocomplete';
15-
import { theme, promqlHighlighter } from './CMTheme';
15+
import { baseTheme, lightTheme, darkTheme, promqlHighlighter } from './CMTheme';
1616
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
1717
import { faSearch, faSpinner } from '@fortawesome/free-solid-svg-icons';
1818
import { newCompleteStrategy } from 'codemirror-promql/cjs/complete';
1919
import PathPrefixProps from '../../types/PathPrefixProps';
20+
import { useTheme } from '../../contexts/ThemeContext';
2021

2122
const promqlExtension = new PromQLExtension();
2223

@@ -88,6 +89,7 @@ const CMExpressionInput: FC<PathPrefixProps & CMExpressionInputProps> = ({
8889
}) => {
8990
const containerRef = useRef<HTMLDivElement>(null);
9091
const viewRef = useRef<EditorView | null>(null);
92+
const { theme } = useTheme();
9193

9294
// (Re)initialize editor based on settings / setting changes.
9395
useEffect(() => {
@@ -103,7 +105,11 @@ const CMExpressionInput: FC<PathPrefixProps & CMExpressionInputProps> = ({
103105
queryHistory
104106
),
105107
});
106-
const dynamicConfig = [enableHighlighting ? promqlHighlighter : [], promqlExtension.asExtension()];
108+
const dynamicConfig = [
109+
enableHighlighting ? promqlHighlighter : [],
110+
promqlExtension.asExtension(),
111+
theme === 'dark' ? darkTheme : lightTheme,
112+
];
107113

108114
// Create or reconfigure the editor.
109115
const view = viewRef.current;
@@ -116,7 +122,7 @@ const CMExpressionInput: FC<PathPrefixProps & CMExpressionInputProps> = ({
116122
const startState = EditorState.create({
117123
doc: value,
118124
extensions: [
119-
theme,
125+
baseTheme,
120126
highlightSpecialChars(),
121127
history(),
122128
EditorState.allowMultipleSelections.of(true),
@@ -189,7 +195,7 @@ const CMExpressionInput: FC<PathPrefixProps & CMExpressionInputProps> = ({
189195
// re-run this effect every time that "value" changes.
190196
//
191197
// eslint-disable-next-line react-hooks/exhaustive-deps
192-
}, [enableAutocomplete, enableHighlighting, enableLinter, executeQuery, onExpressionChange, queryHistory]);
198+
}, [enableAutocomplete, enableHighlighting, enableLinter, executeQuery, onExpressionChange, queryHistory, theme]);
193199

194200
return (
195201
<>

0 commit comments

Comments
 (0)