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:
parent
19bbc337bf
commit
22ee2ca08d
12 changed files with 242 additions and 91 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -8,7 +8,7 @@
|
|||
# testing
|
||||
/coverage
|
||||
|
||||
# production
|
||||
# building
|
||||
/build
|
||||
|
||||
# misc
|
||||
|
|
16
Dockerfile
16
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" ]
|
||||
RUN [ "yarn", "build" ]
|
||||
|
||||
# Expose the two relevant ports
|
||||
EXPOSE 3000 8080
|
||||
|
||||
# Serve the app
|
||||
CMD [ "yarn", "serve:production" ]
|
40
README.md
40
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
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"apps": [
|
||||
{
|
||||
"name": "my pi hole",
|
||||
"name": "Pihole",
|
||||
"displayURL": "example.com",
|
||||
"URL": "https://example.com",
|
||||
"icon": "vpn_lock"
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
@ -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 (
|
||||
<ListContainer>
|
||||
<Headline>Applications</Headline>
|
||||
<RefreshButton onClick={fetchAppData}>refresh</RefreshButton>
|
||||
<ItemList>
|
||||
{error && <ErrorMessage>{error}</ErrorMessage>}
|
||||
{apps.map((app, idx) => {
|
||||
|
|
|
@ -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 (
|
||||
<ListContainer>
|
||||
<Headline>Bookmarks</Headline>
|
||||
<RefreshButton onClick={fetchBookmarkData}>refresh</RefreshButton>
|
||||
<ItemList>
|
||||
{error && <ErrorMessage>{error}</ErrorMessage>}
|
||||
{groups.map((group, idx) => {
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
@ -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`
|
||||
|
|
|
@ -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 (
|
||||
<form onSubmit={e => handleSearchQuery(e)}>
|
||||
{searchProviders.error && (
|
||||
<ErrorMessage>{searchProviders.error}</ErrorMessage>
|
||||
)}
|
||||
{error && <ErrorMessage>{error}</ErrorMessage>}
|
||||
<SearchInput
|
||||
type="text"
|
||||
onChange={e => setInput(e.target.value)}
|
||||
|
|
|
@ -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}
|
||||
/>
|
||||
<Button onClick={fetchThemeData}>Refresh</Button>
|
||||
|
||||
<Button
|
||||
onClick={() => setTheme(JSON.stringify(newTheme))}
|
||||
>
|
||||
Apply
|
||||
</Button>
|
||||
<Button onClick={() => window.location.reload()}>
|
||||
Refresh
|
||||
</Button>
|
||||
</FormContainer>
|
||||
</SelectContainer>
|
||||
<Table>
|
||||
|
|
|
@ -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(
|
||||
|
|
Loading…
Reference in a new issue