A lot of improvements

- Removed "refresh" buttons, please just refresh the entire site (less cluttered appearance)
- Refined README on installation
- Added more themes
- Improved Dockerfile, now everything runs with production settings
This commit is contained in:
Bastian Meissner 2020-05-24 15:06:47 +02:00
parent 19bbc337bf
commit 22ee2ca08d
12 changed files with 242 additions and 91 deletions

2
.gitignore vendored
View file

@ -8,7 +8,7 @@
# testing # testing
/coverage /coverage
# production # building
/build /build
# misc # misc

View file

@ -1,6 +1,18 @@
# Get current image of node.js
FROM node:current-slim FROM node:current-slim
# Set /app as directory where the app should be at
WORKDIR /app WORKDIR /app
# Copy all of the relevant files over to the container
COPY . . COPY . .
# Download dependencies, build container
RUN yarn RUN yarn
EXPOSE 3000 RUN [ "yarn", "build" ]
CMD [ "yarn", "start" ]
# Expose the two relevant ports
EXPOSE 3000 8080
# Serve the app
CMD [ "yarn", "serve:production" ]

View file

@ -18,41 +18,27 @@ Getting Dashboard to run is fairly simple and can be accomplished with two techn
1. Locally 1. Locally
**Prerequisites: node, npm, yarn**
To get Dashboard to run, just clone the repository, download the dependencies using yarn, then start using `yarn start`.
``` ```
git clone https://github.com/phntxx/dashboard.git $ git clone https://github.com/phntxx/dashboard.git
cd dashboard $ cd dashboard/
yarn $ yarn
yarn build $ yarn build
yarn serve:production $ yarn serve:production
``` ```
alternatively, if you want to work using static files (requires a rebuild for
every change in the JSON-files), just replace `yarn start` with `yarn build`.
Then you can copy the files inside the `build` directory onto the webroot of
your webserver of choice.
2. Using Docker 2. Using Docker
Using Docker requires building the container manually. Fortunately, this can be accomplished fairly easily:
``` ```
git clone https://github.com/phntxx/dashboard.git $ git clone https://github.com/phntxx/dashboard.git
cd dashboard $ cd dashboard/
docker build -t dashboard:1.0 $ docker build -t dashboard:1.0 .
$ docker run -d \
docker run -d \ -v $(pwd)/data:/app/data
-t \ -p 3000:3000 \
-p 3000:3000 \ --name dashboard \
-v ./src/components/data:/app/src/components/data \ dashboard:1.0
dashboard:1.0
``` ```
**NOTE: The `-t` flag is very important, as the Dockerfile requires standard TTY.**
## Customization ## Customization
Dashboard is designed to be customizable. Everything is handled using four .json-files, which can be found at /src/components/data Dashboard is designed to be customizable. Everything is handled using four .json-files, which can be found at /src/components/data

View file

@ -1,7 +1,7 @@
{ {
"apps": [ "apps": [
{ {
"name": "my pi hole", "name": "Pihole",
"displayURL": "example.com", "displayURL": "example.com",
"URL": "https://example.com", "URL": "https://example.com",
"icon": "vpn_lock" "icon": "vpn_lock"

View file

@ -20,6 +20,90 @@
"mainColor": "", "mainColor": "",
"accentColor": "", "accentColor": "",
"backgroundColor": "#ffffff" "backgroundColor": "#ffffff"
},
{
"label": "Blackboard",
"value": 3,
"mainColor": "#fffdea",
"accentColor": "5c5c5c",
"backgroundColor": "#1a1a1a"
},
{
"label": "Gazette",
"value": 4,
"mainColor": "#000000",
"accentColor": "#5c5c5c",
"backgroundColor": "#F2F7FF"
},
{
"label": "Espresso",
"value": 5,
"mainColor": "#d1b59a",
"accentColor": "#4e4e4e",
"backgroundColor": "#21211f"
},
{
"label": "Cab",
"value": 6,
"mainColor": "#1f1f1f",
"accentColor": "#424242",
"backgroundColor": "#f6d305"
},
{
"label": "Cloud",
"value": 7,
"mainColor": "#35342f",
"accentColor": "#37bbe4",
"backgroundColor": "#f1f2f0"
},
{
"label": "Lime",
"value": 8,
"mainColor": "#aabbc3",
"accentColor": "#aeea00",
"backgroundColor": "#263238"
},
{
"label": "White",
"value": 9,
"mainColor": "#222222",
"accentColor": "#dddddd",
"backgroundColor": "#ffffff"
},
{
"label": "Tron",
"value": 10,
"mainColor": "#effbff",
"accentColor": "#6ee2ff",
"backgroundColor": "#242b33"
},
{
"label": "Blues",
"value": 11,
"mainColor": "#eff1fc",
"accentColor": "#6677eb",
"backgroundColor": "#2b2c56"
},
{
"label": "Passion",
"value": 12,
"mainColor": "#12005e",
"accentColor": "#8e24aa",
"backgroundColor": "#f5f5f5"
},
{
"label": "Chalk",
"value": 13,
"mainColor": "#aabbc3",
"accentColor": "#ff869a",
"backgroundColor": "#263238"
},
{
"label": "Paper",
"value": 14,
"mainColor": "#4c432e",
"accentColor": "#aa9a73",
"backgroundColor": "#f8f6f1"
} }
] ]
} }

View file

@ -5,11 +5,11 @@ import styled from 'styled-components';
import selectedTheme from './themeManager'; import selectedTheme from './themeManager';
import { import {
handleResponse,
Headline, Headline,
ListContainer, ListContainer,
ItemList, ItemList,
Item, Item,
RefreshButton,
ErrorMessage ErrorMessage
} from './elements'; } from './elements';
@ -48,13 +48,6 @@ const App = styled.div`
padding: 1rem; padding: 1rem;
`; `;
const handleResponse = response => {
if (response.ok) {
return response.json();
}
throw new Error('Failed to load app data.');
};
const useAppData = () => { const useAppData = () => {
const [appData, setAppData] = useState({ apps: [], error: false }); const [appData, setAppData] = useState({ apps: [], error: false });
const fetchAppData = useCallback(() => { const fetchAppData = useCallback(() => {
@ -84,7 +77,6 @@ const AppList = () => {
return ( return (
<ListContainer> <ListContainer>
<Headline>Applications</Headline> <Headline>Applications</Headline>
<RefreshButton onClick={fetchAppData}>refresh</RefreshButton>
<ItemList> <ItemList>
{error && <ErrorMessage>{error}</ErrorMessage>} {error && <ErrorMessage>{error}</ErrorMessage>}
{apps.map((app, idx) => { {apps.map((app, idx) => {

View file

@ -3,11 +3,11 @@ import styled from 'styled-components';
import selectedTheme from './themeManager'; import selectedTheme from './themeManager';
import { import {
handleResponse,
Headline, Headline,
ListContainer, ListContainer,
ItemList, ItemList,
Item, Item,
RefreshButton,
ErrorMessage ErrorMessage
} from './elements'; } from './elements';
@ -35,18 +35,12 @@ const Bookmark = styled.a`
font-size: 14px; font-size: 14px;
`; `;
const handleResponse = response => {
if (response.ok) {
return response.json();
}
throw new Error('Failed to load app data.');
};
const useBookmarkData = () => { const useBookmarkData = () => {
const [bookmarkData, setBookmarkData] = useState({ const [bookmarkData, setBookmarkData] = useState({
groups: [], groups: [],
error: false error: false
}); });
const fetchBookmarkData = useCallback(() => { const fetchBookmarkData = useCallback(() => {
(process.env.NODE_ENV === 'production' (process.env.NODE_ENV === 'production'
? fetch('/bookmarks.json').then(handleResponse) ? fetch('/bookmarks.json').then(handleResponse)
@ -74,7 +68,6 @@ const BookmarkList = () => {
return ( return (
<ListContainer> <ListContainer>
<Headline>Bookmarks</Headline> <Headline>Bookmarks</Headline>
<RefreshButton onClick={fetchBookmarkData}>refresh</RefreshButton>
<ItemList> <ItemList>
{error && <ErrorMessage>{error}</ErrorMessage>} {error && <ErrorMessage>{error}</ErrorMessage>}
{groups.map((group, idx) => { {groups.map((group, idx) => {

View file

@ -20,6 +20,90 @@
"mainColor": "", "mainColor": "",
"accentColor": "", "accentColor": "",
"backgroundColor": "#ffffff" "backgroundColor": "#ffffff"
},
{
"label": "Blackboard",
"value": 3,
"mainColor": "#fffdea",
"accentColor": "5c5c5c",
"backgroundColor": "#1a1a1a"
},
{
"label": "Gazette",
"value": 4,
"mainColor": "#000000",
"accentColor": "#5c5c5c",
"backgroundColor": "#F2F7FF"
},
{
"label": "Espresso",
"value": 5,
"mainColor": "#d1b59a",
"accentColor": "#4e4e4e",
"backgroundColor": "#21211f"
},
{
"label": "Cab",
"value": 6,
"mainColor": "#1f1f1f",
"accentColor": "#424242",
"backgroundColor": "#f6d305"
},
{
"label": "Cloud",
"value": 7,
"mainColor": "#35342f",
"accentColor": "#37bbe4",
"backgroundColor": "#f1f2f0"
},
{
"label": "Lime",
"value": 8,
"mainColor": "#aabbc3",
"accentColor": "#aeea00",
"backgroundColor": "#263238"
},
{
"label": "White",
"value": 9,
"mainColor": "#222222",
"accentColor": "#dddddd",
"backgroundColor": "#ffffff"
},
{
"label": "Tron",
"value": 10,
"mainColor": "#effbff",
"accentColor": "#6ee2ff",
"backgroundColor": "#242b33"
},
{
"label": "Blues",
"value": 11,
"mainColor": "#eff1fc",
"accentColor": "#6677eb",
"backgroundColor": "#2b2c56"
},
{
"label": "Passion",
"value": 12,
"mainColor": "#12005e",
"accentColor": "#8e24aa",
"backgroundColor": "#f5f5f5"
},
{
"label": "Chalk",
"value": 13,
"mainColor": "#aabbc3",
"accentColor": "#ff869a",
"backgroundColor": "#263238"
},
{
"label": "Paper",
"value": 14,
"mainColor": "#4c432e",
"accentColor": "#aa9a73",
"backgroundColor": "#f8f6f1"
} }
] ]
} }

View file

@ -5,6 +5,13 @@ import MaterialIcon from 'material-icons-react';
// File for elements that are/can be reused across the entire site. // File for elements that are/can be reused across the entire site.
export const handleResponse = response => {
if (response.ok) {
return response.json();
}
throw new Error('Failed to load data.');
};
export const ListContainer = styled.div` export const ListContainer = styled.div`
padding: 2rem 0 2rem 0; padding: 2rem 0 2rem 0;
`; `;
@ -40,8 +47,8 @@ export const Button = styled.button`
border: 1px solid ${selectedTheme.mainColor}; border: 1px solid ${selectedTheme.mainColor};
color: ${selectedTheme.mainColor}; color: ${selectedTheme.mainColor};
background: none; background: none;
margin-left: 1rem;
min-height: 3em; min-height: 3em;
height: 100%;
`; `;
const StyledButton = styled.button` const StyledButton = styled.button`

View file

@ -1,7 +1,7 @@
import React, { useCallback, useEffect, useState } from 'react'; import React, { useCallback, useEffect, useState } from 'react';
import styled from 'styled-components'; import styled from 'styled-components';
import { ErrorMessage } from './elements'; import { handleResponse, ErrorMessage } from './elements';
import selectedTheme from './themeManager'; import selectedTheme from './themeManager';
@ -15,18 +15,12 @@ const SearchInput = styled.input`
color: ${selectedTheme.mainColor}; color: ${selectedTheme.mainColor};
`; `;
const handleResponse = response => {
if (response.ok) {
return response.json();
}
throw new Error('Failed to load app data.');
};
const useSearchProviders = () => { const useSearchProviders = () => {
const [searchProviders, setSearchProviders] = useState({ const [searchProviders, setSearchProviders] = useState({
providers: [], providers: [],
error: false error: false
}); });
const fetchSearchProviders = useCallback(() => { const fetchSearchProviders = useCallback(() => {
(process.env.NODE_ENV === 'production' (process.env.NODE_ENV === 'production'
? fetch('/search.json').then(handleResponse) ? fetch('/search.json').then(handleResponse)
@ -43,11 +37,14 @@ const useSearchProviders = () => {
useEffect(() => { useEffect(() => {
fetchSearchProviders(); fetchSearchProviders();
}, [fetchSearchProviders]); }, [fetchSearchProviders]);
return searchProviders; return { searchProviders, fetchSearchProviders };
}; };
const SearchBar = () => { const SearchBar = () => {
const searchProviders = useSearchProviders(); const {
searchProviders: { providers, error },
fetchSearchProviders
} = useSearchProviders();
let [input, setInput] = useState(); let [input, setInput] = useState();
@ -70,24 +67,17 @@ const SearchBar = () => {
let searchQuery = queryArray.join(' '); let searchQuery = queryArray.join(' ');
var foundProvider = false; providers.forEach(provider => {
searchProviders.providers.forEach(provider => { if (provider.prefix === prefix)
if (provider.prefix === prefix) {
foundProvider = true;
window.location = provider.url + searchQuery; window.location = provider.url + searchQuery;
}
}); });
if (!foundProvider) { window.location = 'https://google.com/search?q=' + query;
window.location = 'https://google.com/search?q=' + query;
}
}; };
return ( return (
<form onSubmit={e => handleSearchQuery(e)}> <form onSubmit={e => handleSearchQuery(e)}>
{searchProviders.error && ( {error && <ErrorMessage>{error}</ErrorMessage>}
<ErrorMessage>{searchProviders.error}</ErrorMessage>
)}
<SearchInput <SearchInput
type="text" type="text"
onChange={e => setInput(e.target.value)} onChange={e => setInput(e.target.value)}

View file

@ -6,7 +6,17 @@ import Select from 'react-select';
import searchData from './data/search.json'; import searchData from './data/search.json';
import selectedTheme, { setTheme } from './themeManager'; import selectedTheme, { setTheme } from './themeManager';
import { Button, IconButton, ErrorMessage } from './elements'; import {
handleResponse,
Button,
IconButton,
ErrorMessage,
Headline as hl
} from './elements';
const Headline = styled(hl)`
padding: 0.5rem 0 0.5rem 0;
`;
const Modal = styled.div` const Modal = styled.div`
position: absolute; position: absolute;
@ -24,16 +34,8 @@ const SelectContainer = styled.div`
`; `;
const FormContainer = styled.div` const FormContainer = styled.div`
display: flex; display: grid;
flex-direction: row; grid-template-columns: auto auto auto;
flex-wrap: nowrap;
`;
const Headline = styled.h3`
font-family: Roboto, sans-serif;
font-weight: 900;
color: ${selectedTheme.mainColor};
margin-top: 0;
`; `;
const Table = styled.table` const Table = styled.table`
@ -57,6 +59,11 @@ const HeadCell = styled.th`
background: none; background: none;
`; `;
const InfoText = styled.p`
font-weight: 400;
padding: 0.5rem 0 0.5rem 0;
`;
const SelectorStyle = { const SelectorStyle = {
control: provided => ({ control: provided => ({
...provided, ...provided,
@ -102,13 +109,6 @@ const SelectorStyle = {
} }
}; };
const handleResponse = response => {
if (response.ok) {
return response.json();
}
throw new Error('Failed to load app data.');
};
const useThemeData = () => { const useThemeData = () => {
const [themeData, setThemeData] = useState({ themes: [], error: false }); const [themeData, setThemeData] = useState({ themes: [], error: false });
const fetchThemeData = useCallback(() => { const fetchThemeData = useCallback(() => {
@ -162,12 +162,15 @@ const SettingsModal = () => {
}} }}
styles={SelectorStyle} styles={SelectorStyle}
/> />
<Button onClick={fetchThemeData}>Refresh</Button>
<Button <Button
onClick={() => setTheme(JSON.stringify(newTheme))} onClick={() => setTheme(JSON.stringify(newTheme))}
> >
Apply Apply
</Button> </Button>
<Button onClick={() => window.location.reload()}>
Refresh
</Button>
</FormContainer> </FormContainer>
</SelectContainer> </SelectContainer>
<Table> <Table>

View file

@ -1,6 +1,6 @@
import React from 'react'; import React from 'react';
import ReactDOM from 'react-dom'; import ReactDOM from 'react-dom';
import App from './App'; import App from './app';
import * as serviceWorker from './serviceWorker'; import * as serviceWorker from './serviceWorker';
ReactDOM.render( ReactDOM.render(