diff --git a/package.json b/package.json
index 3322b17..317829f 100644
--- a/package.json
+++ b/package.json
@@ -6,6 +6,7 @@
"dependencies": {
"@material-ui/core": "^4.1.2",
"@material-ui/icons": "^4.2.1",
+ "axios": "^0.19.0",
"fuzzyset.js": "^0.0.8",
"gh-pages": "^2.0.1",
"i18next": "^17.0.4",
diff --git a/src/containers/home/home.js b/src/containers/home/home.js
index f68af42..c49b05f 100644
--- a/src/containers/home/home.js
+++ b/src/containers/home/home.js
@@ -13,12 +13,19 @@ import {
DialogTitle,
FormControl,
Grid,
+ IconButton,
Input,
InputAdornment,
+ InputLabel,
List,
ListItem,
ListItemText,
+ MenuItem,
+ OutlinedInput,
Paper,
+ Select,
+ Snackbar,
+ SnackbarContent,
Tab,
Tabs,
TextField,
@@ -31,10 +38,16 @@ import SearchIcon from '@material-ui/icons/Search';
import CloudDownloadIcon from '@material-ui/icons/CloudDownload';
import WarningIcon from '@material-ui/icons/Warning';
import BuildIcon from '@material-ui/icons/Build';
+import CloseIcon from '@material-ui/icons/Close';
+import ErrorIcon from '@material-ui/icons/Error';
import './home.scss';
import {withTranslation} from 'react-i18next';
import FuzzySet from 'fuzzyset.js';
+import DataService from '../../services/data';
+
+const buildStatusCheckInterval = 5000;
+
const useStylesSearch = makeStyles(theme => ({
root: {
borderColor: '#e2e2e1',
@@ -54,6 +67,56 @@ const useStylesSearch = makeStyles(theme => ({
focused: {},
}));
+const SnackBarStyles = makeStyles(theme => ({
+ error: {
+ backgroundColor: theme.palette.error.dark,
+ },
+ message: {
+ display: 'flex',
+ alignItems: 'center',
+ },
+ icon: {
+ marginRight: '20px',
+ fontSize: 20,
+ },
+}));
+
+function ErrorSnackBar({open, closeHandle, errorMessage}) {
+ const classes = SnackBarStyles();
+ return (
+
+
+
+ {errorMessage ||
+ 'An unexpected error occurred. Please try again'}
+
+ }
+ action={[
+
+
+ ,
+ ]}
+ />
+
+ );
+}
+
function SearchTextField(props) {
const classes = useStylesSearch();
@@ -86,28 +149,28 @@ function TabContainer({children, dir}) {
);
}
-function AlertDialog({open, handleClose, text, title, t}) {
+function AlertDialog({open, cancelHandler, acceptHandler, text, title, cancelComponent, acceptComponent}) {
return (
@@ -146,6 +209,7 @@ class Home extends React.Component {
'luci'];
deviceNames = [];
deviceNamesID = {};
+ checkBuildStatus;
state = {
showDeviceData: false,
device: {},
@@ -158,36 +222,59 @@ class Home extends React.Component {
query: '',
downloading: false,
packages: this.packages,
- release_version_number: '',
+ distributions: {
+ versions: {},
+ },
+ configChanged: true,
packageName: '',
+ release: '',
+ builtImages: [],
+ isBuilding: false,
+ showUnexpectedErrorBar: false,
};
+
fuzzySet;
basicInterface = 0;
confirmingBuild = false;
- getDevicesData = () => fetch(
- 'https://chef.libremesh.org/download/json/devices.json')
- .then(res => res.json());
- getDeviceData = (device_id) => fetch(
- 'https://chef.libremesh.org/download/json/' + device_id + '.json')
- .then(res => res.json());
+ dataService = new DataService();
componentDidMount() {
- this.getDevicesData().then(data => {
- Object.keys(data['devices']).forEach((device_id) => {
- const device_name = data['devices'][device_id];
- this.deviceNames.push(device_name);
- this.deviceNamesID[device_name] = device_id;
- });
- this.fuzzySet = FuzzySet(this.deviceNames);
+ this.dataService.getDistributions.then(distros => {
this.setState({
- devices: data['devices'],
- devicesLoaded: true,
- release_version_number: data['version_number'],
+ distributions: distros['openwrt'],
+ release: distros['openwrt']['latest'],
+ });
+ this.dataService.getDevicesData.then(data => {
+ Object.keys(data['devices']).forEach((device_name) => {
+ // const device_name = data['devices'][device_id];
+ // this.deviceNames.push(device_name);
+ // this.deviceNamesID[device_name] = device_id;
+ const device_id = data['devices'][device_name];
+ this.deviceNames.push(device_name);
+ this.deviceNamesID[device_name] = device_id;
+ });
+ this.fuzzySet = FuzzySet(this.deviceNames);
+ this.setState({
+ devices: data['devices'],
+ devicesLoaded: true,
+ });
});
});
}
+ closeUnexpectedErrorBar = () => {
+ this.setState({
+ showUnexpectedErrorBar: false,
+ });
+ };
+
+ setRelease = (event) => {
+ this.setState({
+ release: event.target.value,
+ });
+ };
+
selectDevice = (device_name) => {
if (device_name != null) {
const device_id = this.deviceNamesID[device_name];
@@ -197,7 +284,7 @@ class Home extends React.Component {
query: device_name,
deviceLoaded: false,
});
- this.getDeviceData(device_id).then(data => {
+ this.dataService.getDeviceData(device_id).then(data => {
this.setState({
device: data,
deviceLoaded: true,
@@ -244,7 +331,7 @@ class Home extends React.Component {
this.setState({
downloading: false,
});
- }, 1000);
+ }, 2000);
};
changeAddPackageInput = (event) => {
@@ -258,6 +345,7 @@ class Home extends React.Component {
packages.splice(i, 1);
this.setState({
packages,
+ configChanged: true
});
};
@@ -274,19 +362,97 @@ class Home extends React.Component {
this.setState({
packages,
packageName: '',
+ configChanged: true
});
}
};
closeConfirmBuildDialog = (v) => {
this.confirmingBuild = false;
- console.log(v);
};
openConfirmBuildDialog = () => {
this.confirmingBuild = true;
};
+ displayBuiltImageData = async (buildStatusResponse) => {
+ console.log(buildStatusResponse);
+ await this.dataService.getFiles(buildStatusResponse.data.files)
+ .then((fileListResponse) => {
+ let builtImages = [];
+ fileListResponse.forEach((file) => {
+ const suffix = file.name.substring(file.name.length - 4);
+ if (suffix === '.bin') {
+ const type = file.name.split('-').reverse()[0].split('.')[0];
+ builtImages.push({
+ url: 'https://chef.libremesh.org' +
+ buildStatusResponse.data.files + file.name,
+ type,
+ });
+ }
+ });
+ this.setState({
+ builtImages,
+ configChanged: false,
+ isBuilding: false,
+ });
+ });
+ clearTimeout(this.checkBuildStatus);
+ };
+
+ buildImageCheck = async (request_hash) => {
+ const buildStatusResponse = await this.dataService.buildStatusCheck(
+ request_hash);
+ if (buildStatusResponse.status === 202) {
+ this.checkBuildStatus = setTimeout(
+ () => {
+ this.buildImageCheck(request_hash);
+ }, buildStatusCheckInterval,
+ );
+ } else if (buildStatusResponse.status === 200) {
+ await this.displayBuiltImageData(buildStatusResponse);
+ } else {
+ this.setState({
+ isBuilding: false,
+ showUnexpectedErrorBar: true,
+ });
+ }
+ };
+
+ buildImage = async () => {
+ this.closeConfirmBuildDialog();
+ const board = this.state.device.id;
+ const packages = this.state.packages;
+ const target = this.state.device.target + '/' + this.state.device.subtarget;
+ const version = this.state.release;
+ this.setState({
+ isBuilding: true,
+ builtImages: [],
+ });
+ this.dataService.buildImage(board, packages, target, version).then(async res => {
+ if (res.status === 202 && res.data['request_hash'] !== undefined) {
+ const request_hash = res.data['request_hash'];
+ this.checkBuildStatus = setTimeout(
+ async () => {
+ await this.buildImageCheck(request_hash);
+ }, buildStatusCheckInterval,
+ );
+ } else if (res.status === 200) {
+ await this.displayBuiltImageData(res);
+ } else {
+ this.setState({
+ isBuilding: false,
+ showUnexpectedErrorBar: true,
+ });
+ }
+ }).catch(() => {
+ this.setState({
+ isBuilding: false,
+ showUnexpectedErrorBar: true,
+ });
+ });
+ };
+
render() {
const warning432 = this.state.showDeviceData &&
parseInt(
@@ -323,7 +489,28 @@ class Home extends React.Component {
-
+
+
+ {this.props.t('Version')}
+
+
+
+
+ {
+ this.state.showSearch && this.state.searchResults.length !==
+ 0 && (
+
+
+ {
+ this.state.searchResults.map((res, index) => {
+ return (
+ this.selectDevice(res)}
+ >
+
+ {res}
+
+ }/>
+
+ );
+ })
+ }
+
+
+ )
+ }
+ {
+ (this.state.searchResults.length === 0 &&
+ this.state.showSearch) && (
+
+
+
+
+
+ )
+ }
- {
- this.state.showSearch && (
-
-
- {
- this.state.searchResults.map((res, index) => {
- return (
- this.selectDevice(res)}
- >
-
- {res}
-
- }/>
-
- );
- })
- }
-
-
- )
- }
- {
- (this.state.searchResults.length === 0 &&
- this.state.showSearch) && (
-
-
-
-
-
- )
- }
{
@@ -443,6 +631,7 @@ class Home extends React.Component {
return (
this.deletePackage(
i)}
label={package_name}
@@ -462,12 +651,49 @@ class Home extends React.Component {
-
-
-
- {this.props.t('Build')}
-
+ {
+ this.state.configChanged && !this.state.isBuilding && (
+
+
+
+ {this.props.t('Build')}
+
+ )
+ }
+ {
+ this.state.isBuilding && (
+
+ )
+ }
+ {
+ this.state.builtImages.length > 0 && !this.state.configChanged && (
+ <>
+ {
+ this.state.builtImages.map((image) => (
+ this.downloadingImageIndicatorShow()}
+ >
+
+ {image.type}
+
+ ))
+ }
+
+ {
+ this.state.downloading && (
+
+ )
+ }
+ >
+ )
+ }
)
@@ -478,16 +704,32 @@ class Home extends React.Component {
>
);
return (
-
-
-
- {this.state.devicesLoaded ? onLoad : notLoaded}
-
-
+ <>
+
+
+ {this.props.t('Build')}
+ >
+ }
+ />
+
+
+ {this.state.devicesLoaded ? onLoad : notLoaded}
+
+
+ >
);
}
}
diff --git a/src/containers/home/home.scss b/src/containers/home/home.scss
index 76ec9a0..8b41c41 100644
--- a/src/containers/home/home.scss
+++ b/src/containers/home/home.scss
@@ -22,16 +22,38 @@
margin-bottom: 15px;
}
- .search-label {
- background-color: #fff;
- padding: 0 10px;
- position: absolute;
- white-space: nowrap;
+ .version-select {
+ width: 200px;
+
+ .version-label {
+ background-color: inherit;
+ padding: 5px;
+ margin-top: -10px;
+ margin-left: 10px;
+ display: block;
+ z-index: 10;
+ }
}
- .search-results {
- position: absolute;
- z-index: 10;
+ .search-field {
+ width: calc(100% - 220px);
+ margin-left: 20px;
+ position: relative;
+
+ .search-label {
+ background-color: #fff;
+ padding: 0 10px;
+ position: absolute;
+ white-space: nowrap;
+ }
+
+ .search-results {
+ position: absolute;
+ left: 0;
+ width: 100%;
+ top: 100%;
+ z-index: 10;
+ }
}
.MuiTypography-h4 {
diff --git a/src/services/data.js b/src/services/data.js
new file mode 100644
index 0000000..c1f6471
--- /dev/null
+++ b/src/services/data.js
@@ -0,0 +1,47 @@
+import axios from 'axios';
+
+const base = 'https://cors-anywhere.herokuapp.com/https://mwarning.de/misc/json/bin';
+
+class DataService {
+ getDevicesData = axios.get(
+ `${base}/overview.json`)
+ .then(res => res.data);
+
+ getDeviceData = (device_id) => axios.get(
+ base + '/targets/' + device_id)
+ .then(res => res.data);
+
+ getDistributions = axios.get(
+ 'https://chef.libremesh.org/api/distributions')
+ .then(res => res.data);
+
+ buildImage = (board, packages, target, version) => {
+ return axios.post('https://chef.libremesh.org/api/build-request', {
+ board,
+ defaults: '',
+ distro: 'openwrt',
+ packages,
+ target,
+ version,
+ });
+ };
+
+ buildStatusCheck = async (request_hash) => {
+ let response = {
+ isBuilt: false,
+ };
+ await axios.get('https://chef.libremesh.org/api/build-request/' + request_hash).then((res) => {
+ response.isBuilt = res.status === 202 && res.data.files !== undefined;
+ response.status = res.status;
+ if (response.isBuilt) {
+ response = {...response, data: res.data}
+ }
+ });
+ return response;
+ };
+
+ getFiles = (files_url) => axios.get('https://chef.libremesh.org' + files_url).then(res => res.data);
+
+}
+
+export default DataService;
diff --git a/yarn.lock b/yarn.lock
index 1de5b8b..93913b0 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1958,6 +1958,14 @@ aws4@^1.8.0:
resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.8.0.tgz#f0e003d9ca9e7f59c7a508945d7b2ef9a04a542f"
integrity sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ==
+axios@^0.19.0:
+ version "0.19.0"
+ resolved "https://registry.yarnpkg.com/axios/-/axios-0.19.0.tgz#8e09bff3d9122e133f7b8101c8fbdd00ed3d2ab8"
+ integrity sha512-1uvKqKQta3KBxIz14F2v06AEHZ/dIoeKfbTRkK1E5oqjDnuEerLmYTgJB5AiQZHJcljpg1TuRzdjDR06qNk0DQ==
+ dependencies:
+ follow-redirects "1.5.10"
+ is-buffer "^2.0.2"
+
axobject-query@^2.0.2:
version "2.0.2"
resolved "https://registry.yarnpkg.com/axobject-query/-/axobject-query-2.0.2.tgz#ea187abe5b9002b377f925d8bf7d1c561adf38f9"
@@ -3266,6 +3274,13 @@ debug@2.6.9, debug@^2.2.0, debug@^2.3.3, debug@^2.6.0, debug@^2.6.8, debug@^2.6.
dependencies:
ms "2.0.0"
+debug@=3.1.0:
+ version "3.1.0"
+ resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261"
+ integrity sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==
+ dependencies:
+ ms "2.0.0"
+
debug@^3.2.5, debug@^3.2.6:
version "3.2.6"
resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.6.tgz#e83d17de16d8a7efb7717edbe5fb10135eee629b"
@@ -4263,6 +4278,13 @@ flush-write-stream@^1.0.0:
inherits "^2.0.3"
readable-stream "^2.3.6"
+follow-redirects@1.5.10:
+ version "1.5.10"
+ resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.5.10.tgz#7b7a9f9aea2fdff36786a94ff643ed07f4ff5e2a"
+ integrity sha512-0V5l4Cizzvqt5D44aTXbFZz+FtyXV1vrDN6qrelxtfYQKW0KO0W2T/hkE8xvGa/540LkZlkaUjO4ailYTFtHVQ==
+ dependencies:
+ debug "=3.1.0"
+
follow-redirects@^1.0.0:
version "1.7.0"
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.7.0.tgz#489ebc198dc0e7f64167bd23b03c4c19b5784c76"
@@ -5221,7 +5243,7 @@ is-buffer@^1.0.2, is-buffer@^1.1.5:
resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be"
integrity sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==
-is-buffer@^2.0.0:
+is-buffer@^2.0.0, is-buffer@^2.0.2:
version "2.0.3"
resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-2.0.3.tgz#4ecf3fcf749cbd1e472689e109ac66261a25e725"
integrity sha512-U15Q7MXTuZlrbymiz95PJpZxu8IlipAp4dtS3wOdgPXx3mqBnslrWU14kxfHB+Py/+2PVKSr37dMAgM2A4uArw==