Skip to content
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

Add a dark theme #8604

Merged
merged 19 commits into from Apr 15, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
17 changes: 10 additions & 7 deletions web/ui/react-app/package.json
Expand Up @@ -15,11 +15,12 @@
"@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.14",
"@fortawesome/free-solid-svg-icons": "^5.7.1",
"@fortawesome/react-fontawesome": "^0.1.4",
"@reach/router": "^1.2.1",
"bootstrap": "^4.2.1",
"bootstrap": "^4.6.0",
"codemirror-promql": "^0.15.0",
"css.escape": "^1.5.1",
"downshift": "^3.4.8",
Expand All @@ -31,18 +32,20 @@
"jsdom": "^16.4.0",
"moment": "^2.24.0",
"moment-timezone": "^0.5.23",
"node-sass": "4.14.1",
"popper.js": "^1.14.3",
"react": "^16.7.0",
"react-copy-to-clipboard": "^5.0.1",
"react-dom": "^16.7.0",
"react-resize-detector": "^5.0.7",
"react-scripts": "3.4.4",
"react-test-renderer": "^16.9.0",
"reactstrap": "^8.0.1",
"reactstrap": "^8.9.0",
"sanitize-html": "^1.20.1",
"tempusdominus-bootstrap-4": "^5.1.2",
"tempusdominus-core": "^5.0.3",
"typescript": "^3.3.3"
"typescript": "^3.3.3",
prymitive marked this conversation as resolved.
Show resolved Hide resolved
"use-media": "^1.4.0"
},
"scripts": {
"start": "react-scripts start",
Expand Down Expand Up @@ -71,16 +74,16 @@
"@types/flot": "0.0.31",
"@types/jest": "^26.0.10",
"@types/jquery": "^3.5.1",
"@types/node": "^12.11.1",
"@types/moment-timezone": "^0.5.10",
"@types/node": "^12.11.1",
"@types/reach__router": "^1.2.6",
"@types/react": "^16.8.2",
"@types/reactstrap": "^8.0.5",
"@types/sanitize-html": "^1.20.2",
"@types/sinon": "^9.0.4",
"@types/react-copy-to-clipboard": "^5.0.0",
"@types/react-dom": "^16.8.0",
"@types/react-resize-detector": "^5.0.0",
"@types/reactstrap": "^8.7.2",
"@types/sanitize-html": "^1.20.2",
"@types/sinon": "^9.0.4",
"@typescript-eslint/eslint-plugin": "2.x",
"@typescript-eslint/parser": "2.x",
"enzyme": "^3.10.0",
Expand Down
2 changes: 1 addition & 1 deletion web/ui/react-app/public/index.html
Expand Up @@ -38,7 +38,7 @@
-->
<title>TITLE_PLACEHOLDER</title>
</head>
<body>
<body class="bootstrap">
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
<!--
Expand Down
57 changes: 38 additions & 19 deletions web/ui/react-app/src/App.tsx
Expand Up @@ -2,10 +2,13 @@ import React, { FC } from 'react';
import Navigation from './Navbar';
import { Container } from 'reactstrap';

import './App.css';
import { Router, Redirect } from '@reach/router';
import useMedia from 'use-media';
import { Alerts, Config, Flags, Rules, ServiceDiscovery, Status, Targets, TSDBStatus, PanelList } from './pages';
import { PathPrefixContext } from './contexts/PathPrefixContext';
import { ThemeContext, themeName, themeSetting } from './contexts/ThemeContext';
import { Theme, themeLocalStorageKey } from './Theme';
import { useLocalStorage } from './hooks/useLocalStorage';

interface AppProps {
consolesLink: string | null;
Expand Down Expand Up @@ -39,28 +42,44 @@ const App: FC<AppProps> = ({ consolesLink }) => {
}
}

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 (
<PathPrefixContext.Provider value={basePath}>
<Navigation consolesLink={consolesLink} />
<Container fluid style={{ paddingTop: 70 }}>
<Router basepath={`${basePath}`}>
<Redirect from="/" to={`graph`} noThrow />
{/*
<ThemeContext.Provider
value={{ theme: theme, userPreference: userTheme, setTheme: (t: themeSetting) => setUserTheme(t) }}
>
<Theme />
<PathPrefixContext.Provider value={basePath}>
<Navigation consolesLink={consolesLink} />
<Container fluid style={{ paddingTop: 70 }}>
<Router basepath={`${basePath}`}>
<Redirect from="/" to={`graph`} noThrow />
{/*
NOTE: Any route added here needs to also be added to the list of
React-handled router paths ("reactRouterPaths") in /web/web.go.
*/}
<PanelList path="/graph" />
<Alerts path="/alerts" />
<Config path="/config" />
<Flags path="/flags" />
<Rules path="/rules" />
<ServiceDiscovery path="/service-discovery" />
<Status path="/status" />
<TSDBStatus path="/tsdb-status" />
<Targets path="/targets" />
</Router>
</Container>
</PathPrefixContext.Provider>
<PanelList path="/graph" />
<Alerts path="/alerts" />
<Config path="/config" />
<Flags path="/flags" />
<Rules path="/rules" />
<ServiceDiscovery path="/service-discovery" />
<Status path="/status" />
<TSDBStatus path="/tsdb-status" />
<Targets path="/targets" />
</Router>
</Container>
</PathPrefixContext.Provider>
</ThemeContext.Provider>
);
};

Expand Down
4 changes: 3 additions & 1 deletion web/ui/react-app/src/Navbar.tsx
Expand Up @@ -13,6 +13,7 @@ import {
DropdownItem,
} from 'reactstrap';
import { usePathPrefix } from './contexts/PathPrefixContext';
import { ThemeToggle } from './Theme';

interface NavbarProps {
consolesLink: string | null;
Expand All @@ -24,7 +25,7 @@ const Navigation: FC<NavbarProps> = ({ consolesLink }) => {
const pathPrefix = usePathPrefix();
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 @@ -81,6 +82,7 @@ const Navigation: FC<NavbarProps> = ({ consolesLink }) => {
</NavItem>
</Nav>
</Collapse>
<ThemeToggle />
</Navbar>
);
};
Expand Down
48 changes: 48 additions & 0 deletions web/ui/react-app/src/Theme.tsx
@@ -0,0 +1,48 @@
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>
);
};
22 changes: 22 additions & 0 deletions web/ui/react-app/src/contexts/ThemeContext.tsx
@@ -0,0 +1,22 @@
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 web/ui/react-app/src/index.tsx
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
Expand Up @@ -28,7 +28,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: <Link to={createExpressionLink(`ALERTS{alertname="${rule.name}"}`)}>{rule.name}</Link>
Expand Down
10 changes: 0 additions & 10 deletions web/ui/react-app/src/pages/config/Config.css

This file was deleted.

1 change: 0 additions & 1 deletion web/ui/react-app/src/pages/config/Config.tsx
Expand Up @@ -3,7 +3,6 @@ import { RouteComponentProps } from '@reach/router';
import { Button } from 'reactstrap';
import CopyToClipboard from 'react-copy-to-clipboard';

import './Config.css';
import { withStatusIndicator } from '../../components/withStatusIndicator';
import { useFetch } from '../../hooks/useFetch';
import { usePathPrefix } from '../../contexts/PathPrefixContext';
Expand Down
22 changes: 15 additions & 7 deletions web/ui/react-app/src/pages/graph/CMExpressionInput.tsx
Expand Up @@ -13,13 +13,14 @@ 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, faGlobeEurope } from '@fortawesome/free-solid-svg-icons';
import MetricsExplorer from './MetricsExplorer';
import { usePathPrefix } from '../../contexts/PathPrefixContext';
import { newCompleteStrategy } from 'codemirror-promql/cjs/complete';
import { useTheme } from '../../contexts/ThemeContext';

const promqlExtension = new PromQLExtension();

Expand Down Expand Up @@ -92,6 +93,7 @@ const CMExpressionInput: FC<CMExpressionInputProps> = ({
const viewRef = useRef<EditorView | null>(null);
const [showMetricsExplorer, setShowMetricsExplorer] = useState<boolean>(false);
const pathPrefix = usePathPrefix();
const { theme } = useTheme();

// (Re)initialize editor based on settings / setting changes.
useEffect(() => {
Expand All @@ -107,7 +109,11 @@ const CMExpressionInput: FC<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 @@ -120,7 +126,7 @@ const CMExpressionInput: FC<CMExpressionInputProps> = ({
const startState = EditorState.create({
doc: value,
extensions: [
theme,
baseTheme,
highlightSpecialChars(),
history(),
EditorState.allowMultipleSelections.of(true),
Expand Down Expand Up @@ -193,7 +199,7 @@ const CMExpressionInput: FC<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]);

const insertAtCursor = (value: string) => {
const view = viewRef.current;
Expand All @@ -218,11 +224,13 @@ const CMExpressionInput: FC<CMExpressionInputProps> = ({
</InputGroupAddon>
<div ref={containerRef} className="cm-expression-input" />
<InputGroupAddon addonType="append">
<Button className="btn-light border" title="Open metrics explorer" onClick={() => setShowMetricsExplorer(true)}>
<Button
className="metrics-explorer-btn"
title="Open metrics explorer"
onClick={() => setShowMetricsExplorer(true)}
>
<FontAwesomeIcon icon={faGlobeEurope} />
</Button>
</InputGroupAddon>
<InputGroupAddon addonType="append">
<Button className="execute-btn" color="primary" onClick={executeQuery}>
Execute
</Button>
Expand Down