mirror of
https://git.netzspielplatz.de/docker-multiarch/openwrt-firmware-selector.git
synced 2025-11-08 22:09:25 +00:00
Complete build functionality. Used mwarning's server for data.
Now users can build their images using the Build button and it works fine for the most part. There is still some issue with the file download as there are a lot of them and will be imporved in the future. Using axios now for cross browser support issue of `fetch` Signed-off-by: Sudhanshu Gautam <me@sudhanshug.com>
This commit is contained in:
parent
c05ac4dc2c
commit
d30cf925b1
5 changed files with 424 additions and 90 deletions
|
|
@ -6,6 +6,7 @@
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@material-ui/core": "^4.1.2",
|
"@material-ui/core": "^4.1.2",
|
||||||
"@material-ui/icons": "^4.2.1",
|
"@material-ui/icons": "^4.2.1",
|
||||||
|
"axios": "^0.19.0",
|
||||||
"fuzzyset.js": "^0.0.8",
|
"fuzzyset.js": "^0.0.8",
|
||||||
"gh-pages": "^2.0.1",
|
"gh-pages": "^2.0.1",
|
||||||
"i18next": "^17.0.4",
|
"i18next": "^17.0.4",
|
||||||
|
|
|
||||||
|
|
@ -13,12 +13,19 @@ import {
|
||||||
DialogTitle,
|
DialogTitle,
|
||||||
FormControl,
|
FormControl,
|
||||||
Grid,
|
Grid,
|
||||||
|
IconButton,
|
||||||
Input,
|
Input,
|
||||||
InputAdornment,
|
InputAdornment,
|
||||||
|
InputLabel,
|
||||||
List,
|
List,
|
||||||
ListItem,
|
ListItem,
|
||||||
ListItemText,
|
ListItemText,
|
||||||
|
MenuItem,
|
||||||
|
OutlinedInput,
|
||||||
Paper,
|
Paper,
|
||||||
|
Select,
|
||||||
|
Snackbar,
|
||||||
|
SnackbarContent,
|
||||||
Tab,
|
Tab,
|
||||||
Tabs,
|
Tabs,
|
||||||
TextField,
|
TextField,
|
||||||
|
|
@ -31,10 +38,16 @@ import SearchIcon from '@material-ui/icons/Search';
|
||||||
import CloudDownloadIcon from '@material-ui/icons/CloudDownload';
|
import CloudDownloadIcon from '@material-ui/icons/CloudDownload';
|
||||||
import WarningIcon from '@material-ui/icons/Warning';
|
import WarningIcon from '@material-ui/icons/Warning';
|
||||||
import BuildIcon from '@material-ui/icons/Build';
|
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 './home.scss';
|
||||||
import {withTranslation} from 'react-i18next';
|
import {withTranslation} from 'react-i18next';
|
||||||
import FuzzySet from 'fuzzyset.js';
|
import FuzzySet from 'fuzzyset.js';
|
||||||
|
|
||||||
|
import DataService from '../../services/data';
|
||||||
|
|
||||||
|
const buildStatusCheckInterval = 5000;
|
||||||
|
|
||||||
const useStylesSearch = makeStyles(theme => ({
|
const useStylesSearch = makeStyles(theme => ({
|
||||||
root: {
|
root: {
|
||||||
borderColor: '#e2e2e1',
|
borderColor: '#e2e2e1',
|
||||||
|
|
@ -54,6 +67,56 @@ const useStylesSearch = makeStyles(theme => ({
|
||||||
focused: {},
|
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 (
|
||||||
|
<Snackbar
|
||||||
|
anchorOrigin={{
|
||||||
|
vertical: 'bottom',
|
||||||
|
horizontal: 'left',
|
||||||
|
}}
|
||||||
|
open={open}
|
||||||
|
autoHideDuration={6000}
|
||||||
|
onClose={closeHandle}
|
||||||
|
ContentProps={{
|
||||||
|
'aria-describedby': 'message-id',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<SnackbarContent
|
||||||
|
className={classes.error}
|
||||||
|
aria-describedby="client-snackbar"
|
||||||
|
message={
|
||||||
|
<span id="client-snackbar" className={classes.message}>
|
||||||
|
<ErrorIcon className={classes.icon}/>
|
||||||
|
{errorMessage ||
|
||||||
|
'An unexpected error occurred. Please try again'}
|
||||||
|
</span>
|
||||||
|
}
|
||||||
|
action={[
|
||||||
|
<IconButton key="close" aria-label="Close" color="inherit"
|
||||||
|
onClick={closeHandle}>
|
||||||
|
<CloseIcon/>
|
||||||
|
</IconButton>,
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
</Snackbar>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
function SearchTextField(props) {
|
function SearchTextField(props) {
|
||||||
const classes = useStylesSearch();
|
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 (
|
return (
|
||||||
<Dialog
|
<Dialog
|
||||||
open={open}
|
open={open}
|
||||||
onClose={() => handleClose(-1)}
|
onClose={cancelHandler}
|
||||||
aria-labelledby="alert-dialog-title"
|
aria-labelledby="alert-dialog-title"
|
||||||
aria-describedby="alert-dialog-description"
|
aria-describedby="alert-dialog-description"
|
||||||
>
|
>
|
||||||
<DialogTitle
|
<DialogTitle
|
||||||
id="alert-dialog-title">{t(title)}</DialogTitle>
|
id="alert-dialog-title">{title}</DialogTitle>
|
||||||
<DialogContent>
|
<DialogContent>
|
||||||
<DialogContentText id="alert-dialog-description">
|
<DialogContentText id="alert-dialog-description">
|
||||||
{t(text)}
|
{text}
|
||||||
</DialogContentText>
|
</DialogContentText>
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
<DialogActions>
|
<DialogActions>
|
||||||
<Button onClick={() => handleClose(1)} color="primary">
|
<Button onClick={acceptHandler} color="primary">
|
||||||
{t('Build')} <BuildIcon/>
|
{acceptComponent}
|
||||||
</Button>
|
</Button>
|
||||||
<Button onClick={() => handleClose(0)} color="secondary"
|
<Button onClick={cancelHandler} color="secondary"
|
||||||
variant="contained" autoFocus>
|
variant="contained" autoFocus>
|
||||||
{t('Cancel')}
|
{cancelComponent}
|
||||||
</Button>
|
</Button>
|
||||||
</DialogActions>
|
</DialogActions>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
|
|
@ -146,6 +209,7 @@ class Home extends React.Component {
|
||||||
'luci'];
|
'luci'];
|
||||||
deviceNames = [];
|
deviceNames = [];
|
||||||
deviceNamesID = {};
|
deviceNamesID = {};
|
||||||
|
checkBuildStatus;
|
||||||
state = {
|
state = {
|
||||||
showDeviceData: false,
|
showDeviceData: false,
|
||||||
device: {},
|
device: {},
|
||||||
|
|
@ -158,24 +222,35 @@ class Home extends React.Component {
|
||||||
query: '',
|
query: '',
|
||||||
downloading: false,
|
downloading: false,
|
||||||
packages: this.packages,
|
packages: this.packages,
|
||||||
release_version_number: '',
|
distributions: {
|
||||||
|
versions: {},
|
||||||
|
},
|
||||||
|
configChanged: true,
|
||||||
packageName: '',
|
packageName: '',
|
||||||
|
release: '',
|
||||||
|
builtImages: [],
|
||||||
|
isBuilding: false,
|
||||||
|
showUnexpectedErrorBar: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
fuzzySet;
|
fuzzySet;
|
||||||
basicInterface = 0;
|
basicInterface = 0;
|
||||||
confirmingBuild = false;
|
confirmingBuild = false;
|
||||||
|
|
||||||
getDevicesData = () => fetch(
|
dataService = new DataService();
|
||||||
'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());
|
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
this.getDevicesData().then(data => {
|
this.dataService.getDistributions.then(distros => {
|
||||||
Object.keys(data['devices']).forEach((device_id) => {
|
this.setState({
|
||||||
const device_name = data['devices'][device_id];
|
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.deviceNames.push(device_name);
|
||||||
this.deviceNamesID[device_name] = device_id;
|
this.deviceNamesID[device_name] = device_id;
|
||||||
});
|
});
|
||||||
|
|
@ -183,11 +258,23 @@ class Home extends React.Component {
|
||||||
this.setState({
|
this.setState({
|
||||||
devices: data['devices'],
|
devices: data['devices'],
|
||||||
devicesLoaded: true,
|
devicesLoaded: true,
|
||||||
release_version_number: data['version_number'],
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
closeUnexpectedErrorBar = () => {
|
||||||
|
this.setState({
|
||||||
|
showUnexpectedErrorBar: false,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
setRelease = (event) => {
|
||||||
|
this.setState({
|
||||||
|
release: event.target.value,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
selectDevice = (device_name) => {
|
selectDevice = (device_name) => {
|
||||||
if (device_name != null) {
|
if (device_name != null) {
|
||||||
const device_id = this.deviceNamesID[device_name];
|
const device_id = this.deviceNamesID[device_name];
|
||||||
|
|
@ -197,7 +284,7 @@ class Home extends React.Component {
|
||||||
query: device_name,
|
query: device_name,
|
||||||
deviceLoaded: false,
|
deviceLoaded: false,
|
||||||
});
|
});
|
||||||
this.getDeviceData(device_id).then(data => {
|
this.dataService.getDeviceData(device_id).then(data => {
|
||||||
this.setState({
|
this.setState({
|
||||||
device: data,
|
device: data,
|
||||||
deviceLoaded: true,
|
deviceLoaded: true,
|
||||||
|
|
@ -244,7 +331,7 @@ class Home extends React.Component {
|
||||||
this.setState({
|
this.setState({
|
||||||
downloading: false,
|
downloading: false,
|
||||||
});
|
});
|
||||||
}, 1000);
|
}, 2000);
|
||||||
};
|
};
|
||||||
|
|
||||||
changeAddPackageInput = (event) => {
|
changeAddPackageInput = (event) => {
|
||||||
|
|
@ -258,6 +345,7 @@ class Home extends React.Component {
|
||||||
packages.splice(i, 1);
|
packages.splice(i, 1);
|
||||||
this.setState({
|
this.setState({
|
||||||
packages,
|
packages,
|
||||||
|
configChanged: true
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -274,19 +362,97 @@ class Home extends React.Component {
|
||||||
this.setState({
|
this.setState({
|
||||||
packages,
|
packages,
|
||||||
packageName: '',
|
packageName: '',
|
||||||
|
configChanged: true
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
closeConfirmBuildDialog = (v) => {
|
closeConfirmBuildDialog = (v) => {
|
||||||
this.confirmingBuild = false;
|
this.confirmingBuild = false;
|
||||||
console.log(v);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
openConfirmBuildDialog = () => {
|
openConfirmBuildDialog = () => {
|
||||||
this.confirmingBuild = true;
|
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() {
|
render() {
|
||||||
const warning432 = this.state.showDeviceData &&
|
const warning432 = this.state.showDeviceData &&
|
||||||
parseInt(
|
parseInt(
|
||||||
|
|
@ -323,7 +489,28 @@ class Home extends React.Component {
|
||||||
<br/>
|
<br/>
|
||||||
<ClickAwayListener onClickAway={this.hideSearchResults}>
|
<ClickAwayListener onClickAway={this.hideSearchResults}>
|
||||||
<div className="search-container">
|
<div className="search-container">
|
||||||
<FormControl fullWidth>
|
<FormControl className="version-select">
|
||||||
|
<InputLabel htmlFor="version-select" className="version-label">
|
||||||
|
{this.props.t('Version')}
|
||||||
|
</InputLabel>
|
||||||
|
<Select
|
||||||
|
value={this.state.release}
|
||||||
|
onChange={this.setRelease}
|
||||||
|
input={<OutlinedInput name="version"
|
||||||
|
id="version-select" labelWidth={60}/>}
|
||||||
|
>
|
||||||
|
{
|
||||||
|
Object.keys(this.state.distributions['versions'])
|
||||||
|
.map((version) => (
|
||||||
|
<MenuItem value={version} key={version}>
|
||||||
|
<em>{version}</em>
|
||||||
|
</MenuItem>
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
</Select>
|
||||||
|
</FormControl>
|
||||||
|
<FormControl className="search-field">
|
||||||
<SearchTextField
|
<SearchTextField
|
||||||
id="outlined-adornment-search-devices"
|
id="outlined-adornment-search-devices"
|
||||||
labeltext={this.props.t('Search your device')}
|
labeltext={this.props.t('Search your device')}
|
||||||
|
|
@ -331,9 +518,9 @@ class Home extends React.Component {
|
||||||
onChange={this.search}
|
onChange={this.search}
|
||||||
onClick={this.search}
|
onClick={this.search}
|
||||||
/>
|
/>
|
||||||
</FormControl>
|
|
||||||
{
|
{
|
||||||
this.state.showSearch && (
|
this.state.showSearch && this.state.searchResults.length !==
|
||||||
|
0 && (
|
||||||
<Paper elevation={4} className="search-results">
|
<Paper elevation={4} className="search-results">
|
||||||
<List>
|
<List>
|
||||||
{
|
{
|
||||||
|
|
@ -368,6 +555,7 @@ class Home extends React.Component {
|
||||||
</Paper>
|
</Paper>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
</FormControl>
|
||||||
</div>
|
</div>
|
||||||
</ClickAwayListener>
|
</ClickAwayListener>
|
||||||
{
|
{
|
||||||
|
|
@ -443,6 +631,7 @@ class Home extends React.Component {
|
||||||
return (
|
return (
|
||||||
<Chip className="package"
|
<Chip className="package"
|
||||||
key={package_name + i}
|
key={package_name + i}
|
||||||
|
size="small"
|
||||||
onDelete={() => this.deletePackage(
|
onDelete={() => this.deletePackage(
|
||||||
i)}
|
i)}
|
||||||
label={package_name}
|
label={package_name}
|
||||||
|
|
@ -462,12 +651,49 @@ class Home extends React.Component {
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</div>
|
</div>
|
||||||
<br/>
|
<br/>
|
||||||
|
{
|
||||||
|
this.state.configChanged && !this.state.isBuilding && (
|
||||||
<Button variant="outlined" color="primary"
|
<Button variant="outlined" color="primary"
|
||||||
onClick={this.openConfirmBuildDialog}>
|
onClick={this.openConfirmBuildDialog}>
|
||||||
<BuildIcon/>
|
<BuildIcon/>
|
||||||
|
|
||||||
{this.props.t('Build')}
|
{this.props.t('Build')}
|
||||||
</Button>
|
</Button>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
{
|
||||||
|
this.state.isBuilding && (
|
||||||
|
<CircularProgress size={20}/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
{
|
||||||
|
this.state.builtImages.length > 0 && !this.state.configChanged && (
|
||||||
|
<>
|
||||||
|
{
|
||||||
|
this.state.builtImages.map((image) => (
|
||||||
|
<Button
|
||||||
|
key={image.url}
|
||||||
|
className="download-button"
|
||||||
|
href={image.url}
|
||||||
|
color="primary"
|
||||||
|
variant="contained"
|
||||||
|
onClick={() => this.downloadingImageIndicatorShow()}
|
||||||
|
>
|
||||||
|
<CloudDownloadIcon
|
||||||
|
className="download-icon"/>
|
||||||
|
{image.type}
|
||||||
|
</Button>
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
this.state.downloading && (
|
||||||
|
<CircularProgress size={20}/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
</Paper>
|
</Paper>
|
||||||
</TabContainer>
|
</TabContainer>
|
||||||
)
|
)
|
||||||
|
|
@ -478,16 +704,32 @@ class Home extends React.Component {
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
return (
|
return (
|
||||||
|
<>
|
||||||
|
<ErrorSnackBar
|
||||||
|
open={this.state.showUnexpectedErrorBar}
|
||||||
|
closeHandle={this.closeUnexpectedErrorBar}
|
||||||
|
/>
|
||||||
|
<AlertDialog
|
||||||
|
cancelHandler={this.closeConfirmBuildDialog}
|
||||||
|
acceptHandler={this.buildImage}
|
||||||
|
open={this.confirmingBuild}
|
||||||
|
text={this.props.t(
|
||||||
|
'Building image requires computation resources, so we would request you to check if this selection is what you want')}
|
||||||
|
title={this.props.t(
|
||||||
|
'Please confirm that you want to perform this action')}
|
||||||
|
cancelComponent={this.props.t('Cancel')}
|
||||||
|
acceptComponent={
|
||||||
|
<>
|
||||||
|
{this.props.t('Build')} <BuildIcon/>
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
/>
|
||||||
<Container className="home-container">
|
<Container className="home-container">
|
||||||
<Paper className="home-container-paper">
|
<Paper className="home-container-paper">
|
||||||
<AlertDialog handleClose={this.closeConfirmBuildDialog}
|
|
||||||
open={this.confirmingBuild}
|
|
||||||
text="Building image requires computation resources, so we would request you to check if this selection is what you want"
|
|
||||||
title="Please confirm that you want to perform this action"
|
|
||||||
t={this.props.t}/>
|
|
||||||
{this.state.devicesLoaded ? onLoad : notLoaded}
|
{this.state.devicesLoaded ? onLoad : notLoaded}
|
||||||
</Paper>
|
</Paper>
|
||||||
</Container>
|
</Container>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,24 @@
|
||||||
margin-bottom: 15px;
|
margin-bottom: 15px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.version-select {
|
||||||
|
width: 200px;
|
||||||
|
|
||||||
|
.version-label {
|
||||||
|
background-color: inherit;
|
||||||
|
padding: 5px;
|
||||||
|
margin-top: -10px;
|
||||||
|
margin-left: 10px;
|
||||||
|
display: block;
|
||||||
|
z-index: 10;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-field {
|
||||||
|
width: calc(100% - 220px);
|
||||||
|
margin-left: 20px;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
.search-label {
|
.search-label {
|
||||||
background-color: #fff;
|
background-color: #fff;
|
||||||
padding: 0 10px;
|
padding: 0 10px;
|
||||||
|
|
@ -31,8 +49,12 @@
|
||||||
|
|
||||||
.search-results {
|
.search-results {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
top: 100%;
|
||||||
z-index: 10;
|
z-index: 10;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.MuiTypography-h4 {
|
.MuiTypography-h4 {
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
|
|
|
||||||
47
src/services/data.js
Normal file
47
src/services/data.js
Normal file
|
|
@ -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;
|
||||||
24
yarn.lock
24
yarn.lock
|
|
@ -1958,6 +1958,14 @@ aws4@^1.8.0:
|
||||||
resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.8.0.tgz#f0e003d9ca9e7f59c7a508945d7b2ef9a04a542f"
|
resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.8.0.tgz#f0e003d9ca9e7f59c7a508945d7b2ef9a04a542f"
|
||||||
integrity sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ==
|
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:
|
axobject-query@^2.0.2:
|
||||||
version "2.0.2"
|
version "2.0.2"
|
||||||
resolved "https://registry.yarnpkg.com/axobject-query/-/axobject-query-2.0.2.tgz#ea187abe5b9002b377f925d8bf7d1c561adf38f9"
|
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:
|
dependencies:
|
||||||
ms "2.0.0"
|
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:
|
debug@^3.2.5, debug@^3.2.6:
|
||||||
version "3.2.6"
|
version "3.2.6"
|
||||||
resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.6.tgz#e83d17de16d8a7efb7717edbe5fb10135eee629b"
|
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"
|
inherits "^2.0.3"
|
||||||
readable-stream "^2.3.6"
|
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:
|
follow-redirects@^1.0.0:
|
||||||
version "1.7.0"
|
version "1.7.0"
|
||||||
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.7.0.tgz#489ebc198dc0e7f64167bd23b03c4c19b5784c76"
|
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"
|
resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be"
|
||||||
integrity sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==
|
integrity sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==
|
||||||
|
|
||||||
is-buffer@^2.0.0:
|
is-buffer@^2.0.0, is-buffer@^2.0.2:
|
||||||
version "2.0.3"
|
version "2.0.3"
|
||||||
resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-2.0.3.tgz#4ecf3fcf749cbd1e472689e109ac66261a25e725"
|
resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-2.0.3.tgz#4ecf3fcf749cbd1e472689e109ac66261a25e725"
|
||||||
integrity sha512-U15Q7MXTuZlrbymiz95PJpZxu8IlipAp4dtS3wOdgPXx3mqBnslrWU14kxfHB+Py/+2PVKSr37dMAgM2A4uArw==
|
integrity sha512-U15Q7MXTuZlrbymiz95PJpZxu8IlipAp4dtS3wOdgPXx3mqBnslrWU14kxfHB+Py/+2PVKSr37dMAgM2A4uArw==
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue