mirror of
https://github.com/hibiken/asynqmon.git
synced 2025-01-19 03:05:53 +08:00
Add dark mode
This commit is contained in:
parent
60391cdfc3
commit
b15fa59cf9
289
ui/src/App.tsx
289
ui/src/App.tsx
@ -2,7 +2,7 @@ import React, { useState } from "react";
|
|||||||
import { connect, ConnectedProps } from "react-redux";
|
import { connect, ConnectedProps } from "react-redux";
|
||||||
import clsx from "clsx";
|
import clsx from "clsx";
|
||||||
import { BrowserRouter as Router, Switch, Route } from "react-router-dom";
|
import { BrowserRouter as Router, Switch, Route } from "react-router-dom";
|
||||||
import { makeStyles } from "@material-ui/core/styles";
|
import { makeStyles, ThemeProvider } from "@material-ui/core/styles";
|
||||||
import AppBar from "@material-ui/core/AppBar";
|
import AppBar from "@material-ui/core/AppBar";
|
||||||
import Drawer from "@material-ui/core/Drawer";
|
import Drawer from "@material-ui/core/Drawer";
|
||||||
import Toolbar from "@material-ui/core/Toolbar";
|
import Toolbar from "@material-ui/core/Toolbar";
|
||||||
@ -26,6 +26,7 @@ import DoubleArrowIcon from "@material-ui/icons/DoubleArrow";
|
|||||||
import CloseIcon from "@material-ui/icons/Close";
|
import CloseIcon from "@material-ui/icons/Close";
|
||||||
import { AppState } from "./store";
|
import { AppState } from "./store";
|
||||||
import { paths } from "./paths";
|
import { paths } from "./paths";
|
||||||
|
import { makeTheme } from "./theme";
|
||||||
import { closeSnackbar } from "./actions/snackbarActions";
|
import { closeSnackbar } from "./actions/snackbarActions";
|
||||||
import ListItemLink from "./components/ListItemLink";
|
import ListItemLink from "./components/ListItemLink";
|
||||||
import SchedulersView from "./views/SchedulersView";
|
import SchedulersView from "./views/SchedulersView";
|
||||||
@ -125,7 +126,10 @@ const useStyles = makeStyles((theme) => ({
|
|||||||
}));
|
}));
|
||||||
|
|
||||||
function mapStateToProps(state: AppState) {
|
function mapStateToProps(state: AppState) {
|
||||||
return { snackbar: state.snackbar };
|
return {
|
||||||
|
snackbar: state.snackbar,
|
||||||
|
isDarkTheme: state.settings.isDarkTheme,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const mapDispatchToProps = {
|
const mapDispatchToProps = {
|
||||||
@ -141,151 +145,152 @@ function SlideUpTransition(props: TransitionProps) {
|
|||||||
function App(props: ConnectedProps<typeof connector>) {
|
function App(props: ConnectedProps<typeof connector>) {
|
||||||
const classes = useStyles();
|
const classes = useStyles();
|
||||||
const [open, setOpen] = useState(true);
|
const [open, setOpen] = useState(true);
|
||||||
const toggleDrawer = () => {
|
const toggleDrawer = () => setOpen(!open);
|
||||||
setOpen(!open);
|
const theme = makeTheme(props.isDarkTheme);
|
||||||
};
|
|
||||||
return (
|
return (
|
||||||
<Router>
|
<ThemeProvider theme={theme}>
|
||||||
<div className={classes.root}>
|
<Router>
|
||||||
<AppBar
|
<div className={classes.root}>
|
||||||
position="absolute"
|
<AppBar
|
||||||
className={classes.appBar}
|
position="absolute"
|
||||||
variant="outlined"
|
className={classes.appBar}
|
||||||
>
|
variant="outlined"
|
||||||
<Toolbar className={classes.toolbar}>
|
|
||||||
<IconButton
|
|
||||||
edge="start"
|
|
||||||
color="inherit"
|
|
||||||
aria-label="open drawer"
|
|
||||||
onClick={toggleDrawer}
|
|
||||||
className={classes.menuButton}
|
|
||||||
>
|
|
||||||
<MenuIcon />
|
|
||||||
</IconButton>
|
|
||||||
<Typography
|
|
||||||
component="h1"
|
|
||||||
variant="h6"
|
|
||||||
color="inherit"
|
|
||||||
noWrap
|
|
||||||
className={classes.title}
|
|
||||||
>
|
|
||||||
Asynq Monitoring
|
|
||||||
</Typography>
|
|
||||||
</Toolbar>
|
|
||||||
</AppBar>
|
|
||||||
<div className={classes.mainContainer}>
|
|
||||||
<Drawer
|
|
||||||
variant="permanent"
|
|
||||||
classes={{
|
|
||||||
paper: clsx(
|
|
||||||
classes.drawerPaper,
|
|
||||||
!open && classes.drawerPaperClose
|
|
||||||
),
|
|
||||||
}}
|
|
||||||
open={open}
|
|
||||||
>
|
>
|
||||||
<Snackbar
|
<Toolbar className={classes.toolbar}>
|
||||||
anchorOrigin={{ vertical: "bottom", horizontal: "left" }}
|
<IconButton
|
||||||
open={props.snackbar.isOpen}
|
edge="start"
|
||||||
autoHideDuration={6000}
|
color="inherit"
|
||||||
onClose={props.closeSnackbar}
|
aria-label="open drawer"
|
||||||
TransitionComponent={SlideUpTransition}
|
onClick={toggleDrawer}
|
||||||
|
className={classes.menuButton}
|
||||||
|
>
|
||||||
|
<MenuIcon />
|
||||||
|
</IconButton>
|
||||||
|
<Typography
|
||||||
|
component="h1"
|
||||||
|
variant="h6"
|
||||||
|
color="inherit"
|
||||||
|
noWrap
|
||||||
|
className={classes.title}
|
||||||
|
>
|
||||||
|
Asynq Monitoring
|
||||||
|
</Typography>
|
||||||
|
</Toolbar>
|
||||||
|
</AppBar>
|
||||||
|
<div className={classes.mainContainer}>
|
||||||
|
<Drawer
|
||||||
|
variant="permanent"
|
||||||
|
classes={{
|
||||||
|
paper: clsx(
|
||||||
|
classes.drawerPaper,
|
||||||
|
!open && classes.drawerPaperClose
|
||||||
|
),
|
||||||
|
}}
|
||||||
|
open={open}
|
||||||
>
|
>
|
||||||
<SnackbarContent
|
<Snackbar
|
||||||
message={props.snackbar.message}
|
anchorOrigin={{ vertical: "bottom", horizontal: "left" }}
|
||||||
className={classes.snackbar}
|
open={props.snackbar.isOpen}
|
||||||
action={
|
autoHideDuration={6000}
|
||||||
<IconButton
|
onClose={props.closeSnackbar}
|
||||||
size="small"
|
TransitionComponent={SlideUpTransition}
|
||||||
aria-label="close"
|
>
|
||||||
color="inherit"
|
<SnackbarContent
|
||||||
onClick={props.closeSnackbar}
|
message={props.snackbar.message}
|
||||||
>
|
className={classes.snackbar}
|
||||||
<CloseIcon
|
action={
|
||||||
className={classes.snackbarCloseIcon}
|
<IconButton
|
||||||
fontSize="small"
|
size="small"
|
||||||
/>
|
aria-label="close"
|
||||||
</IconButton>
|
color="inherit"
|
||||||
}
|
onClick={props.closeSnackbar}
|
||||||
/>
|
>
|
||||||
</Snackbar>
|
<CloseIcon
|
||||||
<div className={classes.appBarSpacer} />
|
className={classes.snackbarCloseIcon}
|
||||||
<div className={classes.sidebarContainer}>
|
fontSize="small"
|
||||||
<List>
|
/>
|
||||||
<div>
|
</IconButton>
|
||||||
<ListItemLink
|
}
|
||||||
to={paths.HOME}
|
|
||||||
primary="Queues"
|
|
||||||
icon={<BarChartIcon />}
|
|
||||||
/>
|
|
||||||
<ListItemLink
|
|
||||||
to={paths.SERVERS}
|
|
||||||
primary="Servers"
|
|
||||||
icon={<DoubleArrowIcon />}
|
|
||||||
/>
|
|
||||||
<ListItemLink
|
|
||||||
to={paths.SCHEDULERS}
|
|
||||||
primary="Schedulers"
|
|
||||||
icon={<ScheduleIcon />}
|
|
||||||
/>
|
|
||||||
<ListItemLink
|
|
||||||
to={paths.REDIS}
|
|
||||||
primary="Redis"
|
|
||||||
icon={<LayersIcon />}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</List>
|
|
||||||
<List>
|
|
||||||
<ListItemLink
|
|
||||||
to={paths.SETTINGS}
|
|
||||||
primary="Settings"
|
|
||||||
icon={<SettingsIcon />}
|
|
||||||
/>
|
/>
|
||||||
<ListItem
|
</Snackbar>
|
||||||
button
|
<div className={classes.appBarSpacer} />
|
||||||
component="a"
|
<div className={classes.sidebarContainer}>
|
||||||
className={classes.listItem}
|
<List>
|
||||||
href="https://github.com/hibiken/asynqmon/issues"
|
<div>
|
||||||
target="_blank"
|
<ListItemLink
|
||||||
>
|
to={paths.HOME}
|
||||||
<ListItemIcon>
|
primary="Queues"
|
||||||
<FeedbackIcon />
|
icon={<BarChartIcon />}
|
||||||
</ListItemIcon>
|
/>
|
||||||
<ListItemText primary="Send Feedback" />
|
<ListItemLink
|
||||||
</ListItem>
|
to={paths.SERVERS}
|
||||||
</List>
|
primary="Servers"
|
||||||
</div>
|
icon={<DoubleArrowIcon />}
|
||||||
</Drawer>
|
/>
|
||||||
<main className={classes.content}>
|
<ListItemLink
|
||||||
<div className={classes.contentWrapper}>
|
to={paths.SCHEDULERS}
|
||||||
<Switch>
|
primary="Schedulers"
|
||||||
<Route exact path={paths.QUEUE_DETAILS}>
|
icon={<ScheduleIcon />}
|
||||||
<TasksView />
|
/>
|
||||||
</Route>
|
<ListItemLink
|
||||||
<Route exact path={paths.SCHEDULERS}>
|
to={paths.REDIS}
|
||||||
<SchedulersView />
|
primary="Redis"
|
||||||
</Route>
|
icon={<LayersIcon />}
|
||||||
<Route exact path={paths.SERVERS}>
|
/>
|
||||||
<ServersView />
|
</div>
|
||||||
</Route>
|
</List>
|
||||||
<Route exact path={paths.REDIS}>
|
<List>
|
||||||
<RedisInfoView />
|
<ListItemLink
|
||||||
</Route>
|
to={paths.SETTINGS}
|
||||||
<Route exact path={paths.SETTINGS}>
|
primary="Settings"
|
||||||
<SettingsView />
|
icon={<SettingsIcon />}
|
||||||
</Route>
|
/>
|
||||||
<Route exact path={paths.HOME}>
|
<ListItem
|
||||||
<DashboardView />
|
button
|
||||||
</Route>
|
component="a"
|
||||||
<Route path="*">
|
className={classes.listItem}
|
||||||
<PageNotFoundView />
|
href="https://github.com/hibiken/asynqmon/issues"
|
||||||
</Route>
|
target="_blank"
|
||||||
</Switch>
|
>
|
||||||
</div>
|
<ListItemIcon>
|
||||||
</main>
|
<FeedbackIcon />
|
||||||
|
</ListItemIcon>
|
||||||
|
<ListItemText primary="Send Feedback" />
|
||||||
|
</ListItem>
|
||||||
|
</List>
|
||||||
|
</div>
|
||||||
|
</Drawer>
|
||||||
|
<main className={classes.content}>
|
||||||
|
<div className={classes.contentWrapper}>
|
||||||
|
<Switch>
|
||||||
|
<Route exact path={paths.QUEUE_DETAILS}>
|
||||||
|
<TasksView />
|
||||||
|
</Route>
|
||||||
|
<Route exact path={paths.SCHEDULERS}>
|
||||||
|
<SchedulersView />
|
||||||
|
</Route>
|
||||||
|
<Route exact path={paths.SERVERS}>
|
||||||
|
<ServersView />
|
||||||
|
</Route>
|
||||||
|
<Route exact path={paths.REDIS}>
|
||||||
|
<RedisInfoView />
|
||||||
|
</Route>
|
||||||
|
<Route exact path={paths.SETTINGS}>
|
||||||
|
<SettingsView />
|
||||||
|
</Route>
|
||||||
|
<Route exact path={paths.HOME}>
|
||||||
|
<DashboardView />
|
||||||
|
</Route>
|
||||||
|
<Route path="*">
|
||||||
|
<PageNotFoundView />
|
||||||
|
</Route>
|
||||||
|
</Switch>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</Router>
|
||||||
</Router>
|
</ThemeProvider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,13 +1,18 @@
|
|||||||
// List of settings related action types.
|
// List of settings related action types.
|
||||||
export const POLL_INTERVAL_CHANGE = "POLL_INTERVAL_CHANGE";
|
export const POLL_INTERVAL_CHANGE = 'POLL_INTERVAL_CHANGE';
|
||||||
|
export const TOGGLE_DARK_THEME = 'TOGGLE_DARK_THEME';
|
||||||
|
|
||||||
interface PollIntervalChangeAction {
|
interface PollIntervalChangeAction {
|
||||||
type: typeof POLL_INTERVAL_CHANGE;
|
type: typeof POLL_INTERVAL_CHANGE;
|
||||||
value: number; // new poll interval value in seconds
|
value: number; // new poll interval value in seconds
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface ToggleDarkThemeAction {
|
||||||
|
type: typeof TOGGLE_DARK_THEME;
|
||||||
|
}
|
||||||
|
|
||||||
// Union of all settings related action types.
|
// Union of all settings related action types.
|
||||||
export type SettingsActionTypes = PollIntervalChangeAction;
|
export type SettingsActionTypes = PollIntervalChangeAction | ToggleDarkThemeAction;
|
||||||
|
|
||||||
export function pollIntervalChange(value: number) {
|
export function pollIntervalChange(value: number) {
|
||||||
return {
|
return {
|
||||||
@ -15,3 +20,10 @@ export function pollIntervalChange(value: number) {
|
|||||||
value,
|
value,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function toggleDarkTheme() {
|
||||||
|
return {
|
||||||
|
type: TOGGLE_DARK_THEME
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@ -2,19 +2,15 @@ import React from "react";
|
|||||||
import ReactDOM from "react-dom";
|
import ReactDOM from "react-dom";
|
||||||
import CssBaseline from "@material-ui/core/CssBaseline";
|
import CssBaseline from "@material-ui/core/CssBaseline";
|
||||||
import { Provider } from "react-redux";
|
import { Provider } from "react-redux";
|
||||||
import { ThemeProvider } from "@material-ui/core/styles";
|
|
||||||
import App from "./App";
|
import App from "./App";
|
||||||
import store from "./store";
|
import store from "./store";
|
||||||
import theme from "./theme";
|
|
||||||
import * as serviceWorker from "./serviceWorker";
|
import * as serviceWorker from "./serviceWorker";
|
||||||
|
|
||||||
ReactDOM.render(
|
ReactDOM.render(
|
||||||
<React.StrictMode>
|
<React.StrictMode>
|
||||||
<CssBaseline />
|
<CssBaseline />
|
||||||
<Provider store={store}>
|
<Provider store={store}>
|
||||||
<ThemeProvider theme={theme}>
|
<App />
|
||||||
<App />
|
|
||||||
</ThemeProvider>
|
|
||||||
</Provider>
|
</Provider>
|
||||||
</React.StrictMode>,
|
</React.StrictMode>,
|
||||||
document.getElementById("root")
|
document.getElementById("root")
|
||||||
|
@ -1,23 +1,24 @@
|
|||||||
import {
|
import {POLL_INTERVAL_CHANGE, SettingsActionTypes, TOGGLE_DARK_THEME,} from '../actions/settingsActions';
|
||||||
POLL_INTERVAL_CHANGE,
|
|
||||||
SettingsActionTypes,
|
|
||||||
} from "../actions/settingsActions";
|
|
||||||
|
|
||||||
interface SettingsState {
|
interface SettingsState {
|
||||||
pollInterval: number;
|
pollInterval: number;
|
||||||
|
isDarkTheme: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const initialState: SettingsState = {
|
const initialState: SettingsState = {
|
||||||
pollInterval: 8,
|
pollInterval: 8,
|
||||||
|
isDarkTheme: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
function settingsReducer(
|
function settingsReducer(
|
||||||
state = initialState,
|
state = initialState, action: SettingsActionTypes): SettingsState {
|
||||||
action: SettingsActionTypes
|
|
||||||
): SettingsState {
|
|
||||||
switch (action.type) {
|
switch (action.type) {
|
||||||
case POLL_INTERVAL_CHANGE:
|
case POLL_INTERVAL_CHANGE:
|
||||||
return { ...state, pollInterval: action.value };
|
return {...state, pollInterval: action.value};
|
||||||
|
case TOGGLE_DARK_THEME:
|
||||||
|
return {
|
||||||
|
...state, isDarkTheme: !state.isDarkTheme
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
|
@ -1,18 +1,19 @@
|
|||||||
import { createMuiTheme } from "@material-ui/core/styles";
|
import { createMuiTheme, Theme } from "@material-ui/core/styles";
|
||||||
|
|
||||||
// Got color palette from https://htmlcolors.com/palette/31/stripe
|
export function makeTheme(isDarkTheme: boolean): Theme {
|
||||||
const theme = createMuiTheme({
|
return createMuiTheme({
|
||||||
palette: {
|
// Got color palette from https://htmlcolors.com/palette/31/stripe
|
||||||
primary: {
|
palette: {
|
||||||
main: "#4379FF",
|
primary: {
|
||||||
|
main: "#4379FF",
|
||||||
|
},
|
||||||
|
secondary: {
|
||||||
|
main: "#97FBD1",
|
||||||
|
},
|
||||||
|
background: {
|
||||||
|
default: "#f5f7f9",
|
||||||
|
},
|
||||||
|
type: isDarkTheme ? "dark" : "light",
|
||||||
},
|
},
|
||||||
secondary: {
|
});
|
||||||
main: "#97FBD1",
|
}
|
||||||
},
|
|
||||||
background: {
|
|
||||||
default: "#f5f7f9",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
export default theme;
|
|
||||||
|
@ -5,8 +5,14 @@ import { makeStyles } from "@material-ui/core/styles";
|
|||||||
import Grid from "@material-ui/core/Grid";
|
import Grid from "@material-ui/core/Grid";
|
||||||
import Paper from "@material-ui/core/Paper";
|
import Paper from "@material-ui/core/Paper";
|
||||||
import Typography from "@material-ui/core/Typography";
|
import Typography from "@material-ui/core/Typography";
|
||||||
import Slider from "@material-ui/core/Slider/Slider";
|
import Switch from "@material-ui/core/Switch";
|
||||||
import { pollIntervalChange } from "../actions/settingsActions";
|
import FormControlLabel from "@material-ui/core/FormControlLabel";
|
||||||
|
import FormGroup from "@material-ui/core/FormGroup";
|
||||||
|
import Slider from "@material-ui/core/Slider";
|
||||||
|
import {
|
||||||
|
pollIntervalChange,
|
||||||
|
toggleDarkTheme,
|
||||||
|
} from "../actions/settingsActions";
|
||||||
import { AppState } from "../store";
|
import { AppState } from "../store";
|
||||||
|
|
||||||
const useStyles = makeStyles((theme) => ({
|
const useStyles = makeStyles((theme) => ({
|
||||||
@ -25,10 +31,11 @@ const useStyles = makeStyles((theme) => ({
|
|||||||
function mapStateToProps(state: AppState) {
|
function mapStateToProps(state: AppState) {
|
||||||
return {
|
return {
|
||||||
pollInterval: state.settings.pollInterval,
|
pollInterval: state.settings.pollInterval,
|
||||||
|
isDarkTheme: state.settings.isDarkTheme,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const mapDispatchToProps = { pollIntervalChange };
|
const mapDispatchToProps = { pollIntervalChange, toggleDarkTheme };
|
||||||
|
|
||||||
const connector = connect(mapStateToProps, mapDispatchToProps);
|
const connector = connect(mapStateToProps, mapDispatchToProps);
|
||||||
|
|
||||||
@ -46,6 +53,9 @@ function SettingsView(props: PropsFromRedux) {
|
|||||||
props.pollIntervalChange(val as number);
|
props.pollIntervalChange(val as number);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleThemeChange = (event: React.ChangeEvent<HTMLElement>) => {
|
||||||
|
props.toggleDarkTheme();
|
||||||
|
};
|
||||||
return (
|
return (
|
||||||
<Container maxWidth="lg" className={classes.container}>
|
<Container maxWidth="lg" className={classes.container}>
|
||||||
<Grid container spacing={3}>
|
<Grid container spacing={3}>
|
||||||
@ -75,6 +85,23 @@ function SettingsView(props: PropsFromRedux) {
|
|||||||
/>
|
/>
|
||||||
</Paper>
|
</Paper>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
<Grid item xs={5}>
|
||||||
|
<Paper>
|
||||||
|
<Typography color="textPrimary">theme switch</Typography>
|
||||||
|
<FormGroup row>
|
||||||
|
<FormControlLabel
|
||||||
|
control={
|
||||||
|
<Switch
|
||||||
|
checked={props.isDarkTheme}
|
||||||
|
onChange={handleThemeChange}
|
||||||
|
name="DarkTheme"
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
label="🌛"
|
||||||
|
/>
|
||||||
|
</FormGroup>
|
||||||
|
</Paper>
|
||||||
|
</Grid>
|
||||||
</Grid>
|
</Grid>
|
||||||
</Container>
|
</Container>
|
||||||
);
|
);
|
||||||
|
Loading…
Reference in New Issue
Block a user