From 22ee2ca08d2ec705f262da8d087e1cfecf35eeea Mon Sep 17 00:00:00 2001 From: Bastian Meissner Date: Sun, 24 May 2020 15:06:47 +0200 Subject: [PATCH] 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 --- .gitignore | 2 +- Dockerfile | 16 ++++++- README.md | 40 +++++----------- data/apps.json | 2 +- data/themes.json | 84 +++++++++++++++++++++++++++++++++ src/components/appList.js | 10 +--- src/components/bookmarkList.js | 11 +---- src/components/data/themes.json | 84 +++++++++++++++++++++++++++++++++ src/components/elements.js | 9 +++- src/components/searchBar.js | 32 +++++-------- src/components/settingsModal.js | 41 ++++++++-------- src/index.js | 2 +- 12 files changed, 242 insertions(+), 91 deletions(-) diff --git a/.gitignore b/.gitignore index 4d29575..7d9df53 100644 --- a/.gitignore +++ b/.gitignore @@ -8,7 +8,7 @@ # testing /coverage -# production +# building /build # misc diff --git a/Dockerfile b/Dockerfile index b7e413e..6d88cf8 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,6 +1,18 @@ +# Get current image of node.js FROM node:current-slim + +# Set /app as directory where the app should be at WORKDIR /app + +# Copy all of the relevant files over to the container COPY . . + +# Download dependencies, build container RUN yarn -EXPOSE 3000 -CMD [ "yarn", "start" ] \ No newline at end of file +RUN [ "yarn", "build" ] + +# Expose the two relevant ports +EXPOSE 3000 8080 + +# Serve the app +CMD [ "yarn", "serve:production" ] \ No newline at end of file diff --git a/README.md b/README.md index 314be84..ce4a054 100644 --- a/README.md +++ b/README.md @@ -18,41 +18,27 @@ Getting Dashboard to run is fairly simple and can be accomplished with two techn 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 -cd dashboard -yarn -yarn build -yarn serve:production +$ git clone https://github.com/phntxx/dashboard.git +$ cd dashboard/ +$ yarn +$ yarn build +$ 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 -Using Docker requires building the container manually. Fortunately, this can be accomplished fairly easily: - ``` -git clone https://github.com/phntxx/dashboard.git -cd dashboard -docker build -t dashboard:1.0 - -docker run -d \ --t \ --p 3000:3000 \ --v ./src/components/data:/app/src/components/data \ -dashboard:1.0 +$ git clone https://github.com/phntxx/dashboard.git +$ cd dashboard/ +$ docker build -t dashboard:1.0 . +$ docker run -d \ + -v $(pwd)/data:/app/data + -p 3000:3000 \ + --name dashboard \ + dashboard:1.0 ``` -**NOTE: The `-t` flag is very important, as the Dockerfile requires standard TTY.** - ## Customization Dashboard is designed to be customizable. Everything is handled using four .json-files, which can be found at /src/components/data diff --git a/data/apps.json b/data/apps.json index d0c3fdc..903c79f 100644 --- a/data/apps.json +++ b/data/apps.json @@ -1,7 +1,7 @@ { "apps": [ { - "name": "my pi hole", + "name": "Pihole", "displayURL": "example.com", "URL": "https://example.com", "icon": "vpn_lock" diff --git a/data/themes.json b/data/themes.json index 10a5543..33be91d 100644 --- a/data/themes.json +++ b/data/themes.json @@ -20,6 +20,90 @@ "mainColor": "", "accentColor": "", "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" } ] } diff --git a/src/components/appList.js b/src/components/appList.js index ecf3ab2..794b22a 100644 --- a/src/components/appList.js +++ b/src/components/appList.js @@ -5,11 +5,11 @@ import styled from 'styled-components'; import selectedTheme from './themeManager'; import { + handleResponse, Headline, ListContainer, ItemList, Item, - RefreshButton, ErrorMessage } from './elements'; @@ -48,13 +48,6 @@ const App = styled.div` padding: 1rem; `; -const handleResponse = response => { - if (response.ok) { - return response.json(); - } - throw new Error('Failed to load app data.'); -}; - const useAppData = () => { const [appData, setAppData] = useState({ apps: [], error: false }); const fetchAppData = useCallback(() => { @@ -84,7 +77,6 @@ const AppList = () => { return ( Applications - refresh {error && {error}} {apps.map((app, idx) => { diff --git a/src/components/bookmarkList.js b/src/components/bookmarkList.js index 190842b..f6f03f7 100644 --- a/src/components/bookmarkList.js +++ b/src/components/bookmarkList.js @@ -3,11 +3,11 @@ import styled from 'styled-components'; import selectedTheme from './themeManager'; import { + handleResponse, Headline, ListContainer, ItemList, Item, - RefreshButton, ErrorMessage } from './elements'; @@ -35,18 +35,12 @@ const Bookmark = styled.a` font-size: 14px; `; -const handleResponse = response => { - if (response.ok) { - return response.json(); - } - throw new Error('Failed to load app data.'); -}; - const useBookmarkData = () => { const [bookmarkData, setBookmarkData] = useState({ groups: [], error: false }); + const fetchBookmarkData = useCallback(() => { (process.env.NODE_ENV === 'production' ? fetch('/bookmarks.json').then(handleResponse) @@ -74,7 +68,6 @@ const BookmarkList = () => { return ( Bookmarks - refresh {error && {error}} {groups.map((group, idx) => { diff --git a/src/components/data/themes.json b/src/components/data/themes.json index 10a5543..33be91d 100644 --- a/src/components/data/themes.json +++ b/src/components/data/themes.json @@ -20,6 +20,90 @@ "mainColor": "", "accentColor": "", "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" } ] } diff --git a/src/components/elements.js b/src/components/elements.js index b109113..bff8a70 100644 --- a/src/components/elements.js +++ b/src/components/elements.js @@ -5,6 +5,13 @@ import MaterialIcon from 'material-icons-react'; // 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` padding: 2rem 0 2rem 0; `; @@ -40,8 +47,8 @@ export const Button = styled.button` border: 1px solid ${selectedTheme.mainColor}; color: ${selectedTheme.mainColor}; background: none; - margin-left: 1rem; min-height: 3em; + height: 100%; `; const StyledButton = styled.button` diff --git a/src/components/searchBar.js b/src/components/searchBar.js index f7f1d11..e577d8e 100644 --- a/src/components/searchBar.js +++ b/src/components/searchBar.js @@ -1,7 +1,7 @@ import React, { useCallback, useEffect, useState } from 'react'; import styled from 'styled-components'; -import { ErrorMessage } from './elements'; +import { handleResponse, ErrorMessage } from './elements'; import selectedTheme from './themeManager'; @@ -15,18 +15,12 @@ const SearchInput = styled.input` color: ${selectedTheme.mainColor}; `; -const handleResponse = response => { - if (response.ok) { - return response.json(); - } - throw new Error('Failed to load app data.'); -}; - const useSearchProviders = () => { const [searchProviders, setSearchProviders] = useState({ providers: [], error: false }); + const fetchSearchProviders = useCallback(() => { (process.env.NODE_ENV === 'production' ? fetch('/search.json').then(handleResponse) @@ -43,11 +37,14 @@ const useSearchProviders = () => { useEffect(() => { fetchSearchProviders(); }, [fetchSearchProviders]); - return searchProviders; + return { searchProviders, fetchSearchProviders }; }; const SearchBar = () => { - const searchProviders = useSearchProviders(); + const { + searchProviders: { providers, error }, + fetchSearchProviders + } = useSearchProviders(); let [input, setInput] = useState(); @@ -70,24 +67,17 @@ const SearchBar = () => { let searchQuery = queryArray.join(' '); - var foundProvider = false; - searchProviders.providers.forEach(provider => { - if (provider.prefix === prefix) { - foundProvider = true; + providers.forEach(provider => { + if (provider.prefix === prefix) window.location = provider.url + searchQuery; - } }); - if (!foundProvider) { - window.location = 'https://google.com/search?q=' + query; - } + window.location = 'https://google.com/search?q=' + query; }; return (
handleSearchQuery(e)}> - {searchProviders.error && ( - {searchProviders.error} - )} + {error && {error}} setInput(e.target.value)} diff --git a/src/components/settingsModal.js b/src/components/settingsModal.js index 9d68039..e8898cc 100644 --- a/src/components/settingsModal.js +++ b/src/components/settingsModal.js @@ -6,7 +6,17 @@ import Select from 'react-select'; import searchData from './data/search.json'; 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` position: absolute; @@ -24,16 +34,8 @@ const SelectContainer = styled.div` `; const FormContainer = styled.div` - display: flex; - flex-direction: row; - flex-wrap: nowrap; -`; - -const Headline = styled.h3` - font-family: Roboto, sans-serif; - font-weight: 900; - color: ${selectedTheme.mainColor}; - margin-top: 0; + display: grid; + grid-template-columns: auto auto auto; `; const Table = styled.table` @@ -57,6 +59,11 @@ const HeadCell = styled.th` background: none; `; +const InfoText = styled.p` + font-weight: 400; + padding: 0.5rem 0 0.5rem 0; +`; + const SelectorStyle = { control: 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 [themeData, setThemeData] = useState({ themes: [], error: false }); const fetchThemeData = useCallback(() => { @@ -162,12 +162,15 @@ const SettingsModal = () => { }} styles={SelectorStyle} /> - + + diff --git a/src/index.js b/src/index.js index 2b64cb7..fa6a253 100644 --- a/src/index.js +++ b/src/index.js @@ -1,6 +1,6 @@ import React from 'react'; import ReactDOM from 'react-dom'; -import App from './App'; +import App from './app'; import * as serviceWorker from './serviceWorker'; ReactDOM.render(