Skip to content

Commit b43bfe3

Browse files
authored
Merge pull request #6 from krestaino/develop
develop > master
2 parents e17588d + d4c2074 commit b43bfe3

24 files changed

+622
-577
lines changed

README.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,7 @@ But as a _very_ brief walk-through:
113113

114114
1. Add endpoints and/or credentials to the `.env` file.
115115
2. Create a new route in [api/src/routes](api/src/routes) with the name of your new service. All files in the routes folder are automatically imported.
116-
3. Create a new component in the [ui/src/components/services](ui/src/components/services) folder and import it into the main app entry point [ui/src/index.js](ui/src/index.js).
116+
3. Create a new component in the [ui/src/components/services](ui/src/components/services) folder and import it into the main services component at [ui/src/components/Services.js](ui/src/components/Services.js).
117117
4. Render the data however you want.
118118

119119
## FAQ

api/package.json

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
2-
"name": "api",
3-
"version": "2.1.0",
2+
"name": "my-dash-api",
3+
"version": "2.2.0",
44
"license": "MIT",
55
"author": "Kevin Restaino <kevinrestaino@gmail.com>",
66
"description": "API for My Dash",

package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "my-dash",
3-
"version": "2.1.0",
3+
"version": "2.2.0",
44
"license": "MIT",
55
"author": "Kevin Restaino <kevinrestaino@gmail.com>",
66
"description": "A developer friendly dashboard for monitoring your self-hosted services with a clean and modern UI.",

ui/package.json

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
2-
"name": "ui",
3-
"version": "2.1.0",
2+
"name": "my-dash-ui",
3+
"version": "2.2.0",
44
"license": "MIT",
55
"author": "Kevin Restaino <kevinrestaino@gmail.com>",
66
"description": "UI for My Dash",

ui/src/api.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,6 @@ export default async endpoint => {
2020
if (error.response.status === 401) {
2121
clearLocalStorage();
2222
}
23-
return false;
23+
throw error;
2424
}
2525
};
+1-2
Loading
+1-2
Loading

ui/src/assets/svg/circle-solid.svg

+3
Loading
Loading

ui/src/assets/svg/moon-solid.svg

+1-2
Loading

ui/src/assets/svg/sun-solid.svg

+1-2
Loading

ui/src/components/Auth.js

+5-4
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,9 @@ export default class Auth extends Component {
1515
window.location.reload();
1616
};
1717

18-
render() {
19-
return (
18+
render = () => (
19+
<div className="container mx-auto max-w-lg py-8 px-4">
20+
<h1>My Dash</h1>
2021
<form onSubmit={this.handleSubmit}>
2122
<div className="box flex mt-8">
2223
<label className="text-gray-600 dark:text-gray-500 text-sm flex flex-1 items-center">
@@ -36,6 +37,6 @@ export default class Auth extends Component {
3637
/>
3738
</div>
3839
</form>
39-
);
40-
}
40+
</div>
41+
);
4142
}

ui/src/components/Error.js

+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import React, { PureComponent } from 'react';
2+
import PropTypes from 'prop-types';
3+
4+
import { ReactComponent as IconError } from '../assets/svg/exclamation-triangle-solid.svg';
5+
6+
export default class Error extends PureComponent {
7+
static propTypes = {
8+
error: PropTypes.string
9+
};
10+
11+
render = () => (
12+
<div className="text-red-600">
13+
<IconError className="mx-auto mt-8 w-4" />
14+
<p className="text-xs text-center mt-4 px-4">{this.props.error}</p>
15+
</div>
16+
);
17+
}

ui/src/components/Loading.js

+1-11
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,5 @@
11
import React, { PureComponent } from 'react';
22

3-
import { ReactComponent as IconError } from '../assets/svg/exclamation-triangle-solid.svg';
4-
53
export default class Loading extends PureComponent {
6-
render() {
7-
return this.props.response === false ? (
8-
<div className="text-red-600">
9-
<IconError className="mx-auto my-8 w-4" />
10-
</div>
11-
) : (
12-
<div className="mx-auto my-8 loader" />
13-
);
14-
}
4+
render = () => <div className="mx-auto my-8 loader" />;
155
}

ui/src/components/Service.js

+54
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import React, { Component } from 'react';
2+
import PropTypes from 'prop-types';
3+
4+
import api from '../api.js';
5+
import Error from './Error.js';
6+
import Loading from './Loading.js';
7+
8+
export default class Service extends Component {
9+
static propTypes = {
10+
endpoint: PropTypes.string,
11+
successFetch: PropTypes.func,
12+
refreshRate: PropTypes.number
13+
};
14+
15+
state = {
16+
error: undefined,
17+
loading: true
18+
};
19+
20+
fetch = async () => {
21+
const { endpoint, successFetch } = this.props;
22+
23+
try {
24+
const data = await api(endpoint);
25+
successFetch({ data });
26+
} catch (error) {
27+
this.setState({ error: error.toString() });
28+
} finally {
29+
this.setState({ loading: false });
30+
}
31+
};
32+
33+
componentDidMount = () => {
34+
this.fetch();
35+
36+
setInterval(() => {
37+
this.fetch();
38+
}, this.props.refreshRate);
39+
};
40+
41+
render() {
42+
const { loading, error } = this.state;
43+
44+
if (error) {
45+
return <Error error={error} />;
46+
}
47+
48+
if (loading) {
49+
return <Loading />;
50+
}
51+
52+
return this.props.children;
53+
}
54+
}

ui/src/components/Services.js

+88
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
import React, { Component } from 'react';
2+
3+
import UptimeRobot from './services/UptimeRobot.js';
4+
import Unifi from './services/Unifi.js';
5+
import Netdata from './services/Netdata.js';
6+
import Seafile from './services/Seafile.js';
7+
import Plex from './services/Plex.js';
8+
9+
import Service from './Service.js';
10+
11+
export default class Services extends Component {
12+
state = {
13+
uptimeRobot: undefined,
14+
unifi: undefined,
15+
netdataDo: undefined,
16+
netdataHome: undefined,
17+
seafile: undefined,
18+
plex: undefined
19+
};
20+
21+
render = () => (
22+
<div className="flex flex-col lg:flex-row max-w-full mx-auto text-sm xl:text-base pt-8">
23+
<div className="lg:w-1/5 px-4">
24+
<h2>Uptime Robot</h2>
25+
<Service
26+
endpoint={process.env.REACT_APP_UPTIME_ROBOT_ENDPOINT}
27+
refreshRate={30000}
28+
successFetch={({ data }) => this.setState({ uptimeRobot: data })}
29+
>
30+
<UptimeRobot data={this.state.uptimeRobot} />
31+
</Service>
32+
</div>
33+
34+
<div className="lg:w-1/5 px-4">
35+
<h2>Unifi</h2>
36+
<Service
37+
endpoint={process.env.REACT_APP_UNIFI_ENDPOINT}
38+
refreshRate={5000}
39+
successFetch={({ data }) => this.setState({ unifi: data })}
40+
>
41+
<Unifi data={this.state.unifi} />
42+
</Service>
43+
</div>
44+
45+
<div className="lg:w-1/5 px-4">
46+
<h2>Netdata</h2>
47+
<ul>
48+
<Service
49+
endpoint={process.env.REACT_APP_NETDATA_DO_ENDPOINT}
50+
refreshRate={5000}
51+
successFetch={({ data }) => this.setState({ netdataDo: data })}
52+
>
53+
<Netdata data={this.state.netdataDo} url={process.env.REACT_APP_NETDATA_DO_URL} />
54+
</Service>
55+
<Service
56+
endpoint={process.env.REACT_APP_NETDATA_HOME_ENDPOINT}
57+
refreshRate={5000}
58+
successFetch={({ data }) => this.setState({ netdataHome: data })}
59+
>
60+
<Netdata data={this.state.netdataHome} url={process.env.REACT_APP_NETDATA_HOME_URL} />
61+
</Service>
62+
</ul>
63+
</div>
64+
65+
<div className="lg:w-1/5 px-4">
66+
<h2>Seafile</h2>
67+
<Service
68+
endpoint={process.env.REACT_APP_SEAFILE_ENDPOINT}
69+
refreshRate={5000}
70+
successFetch={({ data }) => this.setState({ seafile: data })}
71+
>
72+
<Seafile data={this.state.seafile} />
73+
</Service>
74+
</div>
75+
76+
<div className="lg:w-1/5 px-4">
77+
<h2>Plex</h2>
78+
<Service
79+
endpoint={process.env.REACT_APP_PLEX_ENDPOINT}
80+
refreshRate={5000}
81+
successFetch={({ data }) => this.setState({ plex: data })}
82+
>
83+
<Plex data={this.state.plex} />
84+
</Service>
85+
</div>
86+
</div>
87+
);
88+
}

ui/src/components/ThemeToggle.js

+18-20
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { ReactComponent as IconSun } from '../assets/svg/sun-solid.svg';
44
import { ReactComponent as IconMoon } from '../assets/svg/moon-solid.svg';
55

66
export default class ThemeToggle extends Component {
7-
setTheme(theme) {
7+
setTheme = theme => {
88
const htmlSelector = document.querySelector('html');
99

1010
if (theme === 'light') {
@@ -14,30 +14,28 @@ export default class ThemeToggle extends Component {
1414
window.localStorage.setItem('THEME', 'dark');
1515
htmlSelector.classList.add('mode-dark');
1616
}
17-
}
17+
};
1818

19-
componentDidMount() {
19+
componentDidMount = () => {
2020
const theme = window.localStorage.getItem('THEME');
2121

2222
if (theme) {
2323
this.setTheme(theme);
2424
}
25-
}
25+
};
2626

27-
render() {
28-
return (
29-
<div className="absolute top-0 right-0 m-4">
30-
<button className="focus:outline-none dark:hidden" title="Set dark theme" onClick={() => this.setTheme('dark')}>
31-
<IconSun className="w-4 dark:hidden" />
32-
</button>
33-
<button
34-
className="focus:outline-none hidden dark:block"
35-
title="Set light theme"
36-
onClick={() => this.setTheme('light')}
37-
>
38-
<IconMoon className="w-4" />
39-
</button>
40-
</div>
41-
);
42-
}
27+
render = () => (
28+
<div className="absolute top-0 right-0 m-4">
29+
<button className="focus:outline-none dark:hidden" title="Set dark theme" onClick={() => this.setTheme('dark')}>
30+
<IconSun className="w-4 dark:hidden" />
31+
</button>
32+
<button
33+
className="focus:outline-none hidden dark:block"
34+
title="Set light theme"
35+
onClick={() => this.setTheme('light')}
36+
>
37+
<IconMoon className="w-4" />
38+
</button>
39+
</div>
40+
);
4341
}

0 commit comments

Comments
 (0)