Skip to content

Commit

Permalink
UI: add dark theme
Browse files Browse the repository at this point in the history
Signed-off-by: Aman Dwivedi <[email protected]>
  • Loading branch information
Aman-Codes committed May 19, 2021
1 parent eacc686 commit a0a056b
Show file tree
Hide file tree
Showing 28 changed files with 993 additions and 234 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ We use _breaking :warning:_ to mark changes that are not backward compatible (re
- [#4176](https://github.com/thanos-io/thanos/pull/4176) Query API: Adds optional `Stats param` to return stats for query APIs
- [#4125](https://github.com/thanos-io/thanos/pull/4125) Rule: Add `--alert.relabel-config` / `--alert.relabel-config-file` allowing to specify alert relabel configurations like [Prometheus](https://prometheus.io/docs/prometheus/latest/configuration/configuration/#relabel_config)
- [#4211](https://github.com/thanos-io/thanos/pull/4211) Add TLS and basic authentication to Thanos APIs
- [#4249](https://github.com/thanos-io/thanos/pull/4249) UI: add dark theme

### Fixed
-
Expand Down
252 changes: 126 additions & 126 deletions pkg/ui/bindata.go

Large diffs are not rendered by default.

5 changes: 4 additions & 1 deletion pkg/ui/react-app/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
"@codemirror/search": "^0.18.2",
"@codemirror/state": "^0.18.2",
"@codemirror/view": "^0.18.3",
"@forevolve/bootstrap-dark": "^1.0.0",
"@fortawesome/fontawesome-svg-core": "^1.2.34",
"@fortawesome/free-solid-svg-icons": "^5.15.2",
"@fortawesome/react-fontawesome": "^0.1.14",
Expand Down Expand Up @@ -44,9 +45,11 @@
"react-test-renderer": "^16.14.0",
"reactstrap": "^8.9.0",
"sanitize-html": "^2.3.2",
"sass": "^1.32.13",
"tempusdominus-bootstrap-4": "^5.39.0",
"tempusdominus-core": "^5.19.0",
"typescript": "3.9.9",
"use-media": "^1.4.0",
"use-query-params": "^1.1.9"
},
"scripts": {
Expand Down Expand Up @@ -79,12 +82,12 @@
"@types/moment-timezone": "^0.5.30",
"@types/node": "^14.14.30",
"@types/reach__router": "^1.3.7",
"@types/reactstrap": "^8.7.2",
"@types/react": "^17.0.2",
"@types/react-copy-to-clipboard": "^5.0.0",
"@types/react-dom": "^17.0.1",
"@types/react-resize-detector": "^4.0.2",
"@types/react-select": "^4.0.13",
"@types/reactstrap": "^8.7.2",
"@types/sanitize-html": "^1.27.1",
"@types/sinon": "^9.0.10",
"@typescript-eslint/eslint-plugin": "^4.15.1",
Expand Down
2 changes: 1 addition & 1 deletion pkg/ui/react-app/public/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@
-->
<title>Thanos | Highly available Prometheus setup</title>
</head>
<body>
<body class="bootstrap">
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
<!--
Expand Down
76 changes: 47 additions & 29 deletions pkg/ui/react-app/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,16 @@ import React, { FC } from 'react';
import { Container } from 'reactstrap';
import { Router, Redirect, globalHistory } from '@reach/router';
import { QueryParamProvider } from 'use-query-params';
import useMedia from 'use-media';

import { Alerts, Config, Flags, Rules, ServiceDiscovery, Status, Targets, TSDBStatus, PanelList, NotFound } from './pages';
import PathPrefixProps from './types/PathPrefixProps';
import ThanosComponentProps from './thanos/types/ThanosComponentProps';
import Navigation from './thanos/Navbar';
import { Stores, ErrorBoundary, Blocks } from './thanos/pages';

import './App.css';
import { ThemeContext, themeName, themeSetting } from './contexts/ThemeContext';
import { Theme, themeLocalStorageKey } from './Theme';
import { useLocalStorage } from './hooks/useLocalStorage';

const defaultRouteConfig: { [component: string]: string } = {
query: '/graph',
Expand All @@ -20,35 +22,51 @@ const defaultRouteConfig: { [component: string]: string } = {
};

const App: FC<PathPrefixProps & ThanosComponentProps> = ({ pathPrefix, thanosComponent }) => {
const [userTheme, setUserTheme] = useLocalStorage<themeSetting>(themeLocalStorageKey, 'auto');
const browserHasThemes = useMedia('(prefers-color-scheme)');
const browserWantsDarkTheme = useMedia('(prefers-color-scheme: dark)');

let theme: themeName;
if (userTheme !== 'auto') {
theme = userTheme;
} else {
theme = browserHasThemes ? (browserWantsDarkTheme ? 'dark' : 'light') : 'light';
}

return (
<ErrorBoundary>
<Navigation
pathPrefix={pathPrefix}
thanosComponent={thanosComponent}
defaultRoute={defaultRouteConfig[thanosComponent]}
/>
<Container fluid style={{ paddingTop: 70 }}>
<QueryParamProvider reachHistory={globalHistory}>
<Router basepath={`${pathPrefix}`}>
<Redirect from="/" to={`${pathPrefix}${defaultRouteConfig[thanosComponent]}`} />
<ThemeContext.Provider
value={{ theme: theme, userPreference: userTheme, setTheme: (t: themeSetting) => setUserTheme(t) }}
>
<Theme />
<ErrorBoundary>
<Navigation
pathPrefix={pathPrefix}
thanosComponent={thanosComponent}
defaultRoute={defaultRouteConfig[thanosComponent]}
/>
<Container fluid style={{ paddingTop: 70 }}>
<QueryParamProvider reachHistory={globalHistory}>
<Router basepath={`${pathPrefix}`}>
<Redirect from="/" to={`${pathPrefix}${defaultRouteConfig[thanosComponent]}`} />

<PanelList path="/graph" pathPrefix={pathPrefix} />
<Alerts path="/alerts" pathPrefix={pathPrefix} />
<Config path="/config" pathPrefix={pathPrefix} />
<Flags path="/flags" pathPrefix={pathPrefix} />
<Rules path="/rules" pathPrefix={pathPrefix} />
<ServiceDiscovery path="/service-discovery" pathPrefix={pathPrefix} />
<Status path="/status" pathPrefix={pathPrefix} />
<TSDBStatus path="/tsdb-status" pathPrefix={pathPrefix} />
<Targets path="/targets" pathPrefix={pathPrefix} />
<Stores path="/stores" pathPrefix={pathPrefix} />
<Blocks path="/blocks" pathPrefix={pathPrefix} />
<Blocks path="/loaded" pathPrefix={pathPrefix} view="loaded" />
<NotFound pathPrefix={pathPrefix} default defaultRoute={defaultRouteConfig[thanosComponent]} />
</Router>
</QueryParamProvider>
</Container>
</ErrorBoundary>
<PanelList path="/graph" pathPrefix={pathPrefix} />
<Alerts path="/alerts" pathPrefix={pathPrefix} />
<Config path="/config" pathPrefix={pathPrefix} />
<Flags path="/flags" pathPrefix={pathPrefix} />
<Rules path="/rules" pathPrefix={pathPrefix} />
<ServiceDiscovery path="/service-discovery" pathPrefix={pathPrefix} />
<Status path="/status" pathPrefix={pathPrefix} />
<TSDBStatus path="/tsdb-status" pathPrefix={pathPrefix} />
<Targets path="/targets" pathPrefix={pathPrefix} />
<Stores path="/stores" pathPrefix={pathPrefix} />
<Blocks path="/blocks" pathPrefix={pathPrefix} />
<Blocks path="/loaded" pathPrefix={pathPrefix} view="loaded" />
<NotFound pathPrefix={pathPrefix} default defaultRoute={defaultRouteConfig[thanosComponent]} />
</Router>
</QueryParamProvider>
</Container>
</ErrorBoundary>
</ThemeContext.Provider>
);
};

Expand Down
4 changes: 3 additions & 1 deletion pkg/ui/react-app/src/Navbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
DropdownItem,
} from 'reactstrap';
import PathPrefixProps from './types/PathPrefixProps';
import { ThemeToggle } from './Theme';

interface NavbarProps {
consolesLink: string | null;
Expand All @@ -23,7 +24,7 @@ const Navigation: FC<PathPrefixProps & NavbarProps> = ({ pathPrefix, consolesLin
const toggle = () => setIsOpen(!isOpen);
return (
<Navbar className="mb-3" dark color="dark" expand="md" fixed="top">
<NavbarToggler onClick={toggle} />
<NavbarToggler onClick={toggle} className="mr-2" />
<Link className="pt-0 navbar-brand" to={`${pathPrefix}/graph`}>
Prometheus
</Link>
Expand Down Expand Up @@ -80,6 +81,7 @@ const Navigation: FC<PathPrefixProps & NavbarProps> = ({ pathPrefix, consolesLin
</NavItem>
</Nav>
</Collapse>
<ThemeToggle />
</Navbar>
);
};
Expand Down
57 changes: 57 additions & 0 deletions pkg/ui/react-app/src/Theme.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
/*
*
* THIS FILE WAS COPIED INTO THANOS FROM PROMETHEUS
* (LIVING AT https://github.com/prometheus/prometheus/blob/main/web/ui/react-app/src/Theme.tsx),
* THE ORIGINAL CODE WAS LICENSED UNDER AN APACHE 2.0 LICENSE, SEE
* https://github.com/prometheus/prometheus/blob/main/LICENSE.
*
*/

import React, { FC, useEffect } from 'react';
import { Form, Button, ButtonGroup } from 'reactstrap';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faMoon, faSun, faAdjust } from '@fortawesome/free-solid-svg-icons';
import { useTheme } from './contexts/ThemeContext';

export const themeLocalStorageKey = 'user-prefers-color-scheme';

export const Theme: FC = () => {
const { theme } = useTheme();

useEffect(() => {
document.body.classList.toggle('bootstrap-dark', theme === 'dark');
document.body.classList.toggle('bootstrap', theme === 'light');
}, [theme]);

return null;
};

export const ThemeToggle: FC = () => {
const { userPreference, setTheme } = useTheme();

return (
<Form className="ml-auto" inline>
<ButtonGroup size="sm">
<Button
color="secondary"
title="Use light theme"
active={userPreference === 'light'}
onClick={() => setTheme('light')}
>
<FontAwesomeIcon icon={faSun} className={userPreference === 'light' ? 'text-white' : 'text-dark'} />
</Button>
<Button color="secondary" title="Use dark theme" active={userPreference === 'dark'} onClick={() => setTheme('dark')}>
<FontAwesomeIcon icon={faMoon} className={userPreference === 'dark' ? 'text-white' : 'text-dark'} />
</Button>
<Button
color="secondary"
title="Use browser-preferred theme"
active={userPreference === 'auto'}
onClick={() => setTheme('auto')}
>
<FontAwesomeIcon icon={faAdjust} className={userPreference === 'auto' ? 'text-white' : 'text-dark'} />
</Button>
</ButtonGroup>
</Form>
);
};
31 changes: 31 additions & 0 deletions pkg/ui/react-app/src/contexts/ThemeContext.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/*
*
* THIS FILE WAS COPIED INTO THANOS FROM PROMETHEUS
* (LIVING AT https://github.com/prometheus/prometheus/blob/main/web/ui/react-app/src/contexts/ThemeContext.tsx),
* THE ORIGINAL CODE WAS LICENSED UNDER AN APACHE 2.0 LICENSE, SEE
* https://github.com/prometheus/prometheus/blob/main/LICENSE.
*
*/

import React from 'react';

export type themeName = 'light' | 'dark';
export type themeSetting = themeName | 'auto';

export interface ThemeCtx {
theme: themeName;
userPreference: themeSetting;
setTheme: (t: themeSetting) => void;
}

// defaults, will be overriden in App.tsx
export const ThemeContext = React.createContext<ThemeCtx>({
theme: 'light',
userPreference: 'auto',
// eslint-disable-next-line @typescript-eslint/no-empty-function
setTheme: (s: themeSetting) => {},
});

export const useTheme = () => {
return React.useContext(ThemeContext);
};
4 changes: 3 additions & 1 deletion pkg/ui/react-app/src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@ import './globals';
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import 'bootstrap/dist/css/bootstrap.min.css';
import './themes/app.scss';
import './themes/light.scss';
import './themes/dark.scss';
import './fonts/codicon.ttf';
import { isPresent } from './utils';

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ const CollapsibleAlertPanel: FC<CollapsibleAlertPanelProps> = ({ rule, showAnnot
<strong>{rule.name}</strong> ({`${rule.alerts.length} active`})
</Alert>
<Collapse isOpen={open} className="mb-2">
<pre style={{ background: '#f5f5f5', padding: 15 }}>
<pre className="alert-cell">
<code>
<div>
name: <a href={createExternalExpressionLink(`ALERTS{alertname="${rule.name}"}`)}>{rule.name}</a>
Expand Down
10 changes: 0 additions & 10 deletions pkg/ui/react-app/src/pages/config/Config.css

This file was deleted.

1 change: 0 additions & 1 deletion pkg/ui/react-app/src/pages/config/Config.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import { Button } from 'reactstrap';
import CopyToClipboard from 'react-copy-to-clipboard';
import PathPrefixProps from '../../types/PathPrefixProps';

import './Config.css';
import { withStatusIndicator } from '../../components/withStatusIndicator';
import { useFetch } from '../../hooks/useFetch';

Expand Down
14 changes: 10 additions & 4 deletions pkg/ui/react-app/src/pages/graph/CMExpressionInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,12 @@ import { commentKeymap } from '@codemirror/comment';
import { lintKeymap } from '@codemirror/lint';
import { PromQLExtension, CompleteStrategy } from 'codemirror-promql';
import { autocompletion, completionKeymap, CompletionContext, CompletionResult } from '@codemirror/autocomplete';
import { theme, promqlHighlighter } from './CMTheme';
import { baseTheme, lightTheme, darkTheme, promqlHighlighter } from './CMTheme';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faSearch, faSpinner } from '@fortawesome/free-solid-svg-icons';
import { newCompleteStrategy } from 'codemirror-promql/cjs/complete';
import PathPrefixProps from '../../types/PathPrefixProps';
import { useTheme } from '../../contexts/ThemeContext';

const promqlExtension = new PromQLExtension();

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

// (Re)initialize editor based on settings / setting changes.
useEffect(() => {
Expand All @@ -103,7 +105,11 @@ const CMExpressionInput: FC<PathPrefixProps & CMExpressionInputProps> = ({
queryHistory
),
});
const dynamicConfig = [enableHighlighting ? promqlHighlighter : [], promqlExtension.asExtension()];
const dynamicConfig = [
enableHighlighting ? promqlHighlighter : [],
promqlExtension.asExtension(),
theme === 'dark' ? darkTheme : lightTheme,
];

// Create or reconfigure the editor.
const view = viewRef.current;
Expand All @@ -116,7 +122,7 @@ const CMExpressionInput: FC<PathPrefixProps & CMExpressionInputProps> = ({
const startState = EditorState.create({
doc: value,
extensions: [
theme,
baseTheme,
highlightSpecialChars(),
history(),
EditorState.allowMultipleSelections.of(true),
Expand Down Expand Up @@ -189,7 +195,7 @@ const CMExpressionInput: FC<PathPrefixProps & CMExpressionInputProps> = ({
// re-run this effect every time that "value" changes.
//
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [enableAutocomplete, enableHighlighting, enableLinter, executeQuery, onExpressionChange, queryHistory]);
}, [enableAutocomplete, enableHighlighting, enableLinter, executeQuery, onExpressionChange, queryHistory, theme]);

return (
<>
Expand Down
Loading

0 comments on commit a0a056b

Please sign in to comment.