mirror of
https://git.netzspielplatz.de/docker-multiarch/openwrt-firmware-selector.git
synced 2025-11-09 01:49:35 +00:00
Adds tabbed interface for advanced image building and reformats code
An interface tagged Basic and Advanced is introduced. Basic section shows the default openwrt images with download links. Advanced section show the options to choose which packages to use and an option to build the images is introduced. Reformatting of the code is also done according to standard code styling. Signed-off-by: Sudhanshu Gautam <me@sudhanshug.com>
This commit is contained in:
parent
7cab0bfeb5
commit
40f466153b
9 changed files with 584 additions and 401 deletions
|
|
@ -1,32 +1,38 @@
|
|||
import React from "react";
|
||||
import {
|
||||
Container,
|
||||
Paper,
|
||||
Typography,
|
||||
InputAdornment,
|
||||
FormControl,
|
||||
TextField,
|
||||
List,
|
||||
ListItem,
|
||||
ListItemText,
|
||||
CircularProgress,
|
||||
import React from 'react';
|
||||
import {
|
||||
AppBar,
|
||||
Button,
|
||||
Grid,
|
||||
ClickAwayListener,
|
||||
ExpansionPanel,
|
||||
ExpansionPanelSummary,
|
||||
ExpansionPanelDetails,
|
||||
Checkbox,
|
||||
CircularProgress,
|
||||
ClickAwayListener,
|
||||
Container,
|
||||
Dialog,
|
||||
DialogActions,
|
||||
DialogContent,
|
||||
DialogContentText,
|
||||
DialogTitle,
|
||||
FormControl,
|
||||
FormControlLabel,
|
||||
FormGroup,
|
||||
FormControlLabel } from "@material-ui/core";
|
||||
import { fade, makeStyles } from '@material-ui/core/styles';
|
||||
Grid,
|
||||
InputAdornment,
|
||||
List,
|
||||
ListItem,
|
||||
ListItemText,
|
||||
Paper,
|
||||
Tab,
|
||||
Tabs,
|
||||
TextField,
|
||||
Typography,
|
||||
} from '@material-ui/core';
|
||||
import {fade, makeStyles} from '@material-ui/core/styles';
|
||||
|
||||
import SearchIcon from '@material-ui/icons/Search';
|
||||
import CloudDownloadIcon from '@material-ui/icons/CloudDownload';
|
||||
import WarningIcon from '@material-ui/icons/Warning';
|
||||
import ExpandMoreIcon from '@material-ui/icons/ExpandMore';
|
||||
import BuildIcon from '@material-ui/icons/Build';
|
||||
import './home.scss';
|
||||
import { withTranslation } from 'react-i18next';
|
||||
import {withTranslation} from 'react-i18next';
|
||||
import FuzzySet from 'fuzzyset.js';
|
||||
|
||||
const useStylesSearch = makeStyles(theme => ({
|
||||
|
|
@ -52,22 +58,59 @@ function SearchTextField(props) {
|
|||
const classes = useStylesSearch();
|
||||
|
||||
return (
|
||||
<TextField
|
||||
variant="outlined"
|
||||
label={
|
||||
<div className="search-label">
|
||||
{props.labeltext}
|
||||
</div>
|
||||
}
|
||||
InputProps={
|
||||
{ classes,
|
||||
endAdornment: (
|
||||
<InputAdornment position="start">
|
||||
<SearchIcon className={classes.label} />
|
||||
</InputAdornment>
|
||||
)
|
||||
}
|
||||
} {...props} />
|
||||
<TextField
|
||||
variant="outlined"
|
||||
label={
|
||||
<div className="search-label">
|
||||
{props.labeltext}
|
||||
</div>
|
||||
}
|
||||
InputProps={
|
||||
{
|
||||
classes,
|
||||
endAdornment: (
|
||||
<InputAdornment position="start">
|
||||
<SearchIcon className={classes.label}/>
|
||||
</InputAdornment>
|
||||
),
|
||||
}
|
||||
} {...props} />
|
||||
);
|
||||
}
|
||||
|
||||
function TabContainer({children, dir}) {
|
||||
return (
|
||||
<Typography component="div" dir={dir} style={{padding: '20px 0 0'}}>
|
||||
{children}
|
||||
</Typography>
|
||||
);
|
||||
}
|
||||
|
||||
function AlertDialog({open, handleClose, text, title, t}) {
|
||||
return (
|
||||
<Dialog
|
||||
open={open}
|
||||
onClose={() => handleClose(-1)}
|
||||
aria-labelledby="alert-dialog-title"
|
||||
aria-describedby="alert-dialog-description"
|
||||
>
|
||||
<DialogTitle
|
||||
id="alert-dialog-title">{t(title)}</DialogTitle>
|
||||
<DialogContent>
|
||||
<DialogContentText id="alert-dialog-description">
|
||||
{t(text)}
|
||||
</DialogContentText>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button onClick={() => handleClose(1)} color="primary">
|
||||
{t('Build')} <BuildIcon/>
|
||||
</Button>
|
||||
<Button onClick={() => handleClose(0)} color="secondary"
|
||||
variant="contained" autoFocus>
|
||||
{t('Cancel')}
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -87,17 +130,51 @@ class Home extends React.Component {
|
|||
query: '',
|
||||
downloading: false,
|
||||
packages: {},
|
||||
release_version_number: '',
|
||||
};
|
||||
fuzzySet;
|
||||
packages = ["opkg","ip6tables","odhcp6c","base-files","mtd","fstools","kmod-leds-gpio","busybox","wpad-mini","kmod-gpio-button-hotplug","kmod-mt76","logd","swconfig","dnsmasq","dropbear","ppp","netifd","ppp-mod-pppoe","uci","libc","uclient-fetch","kmod-ipt-offload","libgcc","odhcpd-ipv6only","iptables","firewall","luci"];
|
||||
basicInterface = 0;
|
||||
confirmingBuild = false;
|
||||
packages = [
|
||||
'opkg',
|
||||
'ip6tables',
|
||||
'odhcp6c',
|
||||
'base-files',
|
||||
'mtd',
|
||||
'fstools',
|
||||
'kmod-leds-gpio',
|
||||
'busybox',
|
||||
'wpad-mini',
|
||||
'kmod-gpio-button-hotplug',
|
||||
'kmod-mt76',
|
||||
'logd',
|
||||
'swconfig',
|
||||
'dnsmasq',
|
||||
'dropbear',
|
||||
'ppp',
|
||||
'netifd',
|
||||
'ppp-mod-pppoe',
|
||||
'uci',
|
||||
'libc',
|
||||
'uclient-fetch',
|
||||
'kmod-ipt-offload',
|
||||
'libgcc',
|
||||
'odhcpd-ipv6only',
|
||||
'iptables',
|
||||
'firewall',
|
||||
'luci'];
|
||||
|
||||
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());
|
||||
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());
|
||||
|
||||
componentDidMount() {
|
||||
this.getDevicesData().then(data => {
|
||||
Object.keys(data['devices']).forEach((device_id) => {
|
||||
var device_name = data['devices'][device_id];
|
||||
const device_name = data['devices'][device_id];
|
||||
this.deviceNames.push(device_name);
|
||||
this.deviceNamesID[device_name] = device_id;
|
||||
});
|
||||
|
|
@ -105,9 +182,10 @@ class Home extends React.Component {
|
|||
this.setState({
|
||||
devices: data['devices'],
|
||||
devicesLoaded: true,
|
||||
release_version_number: data['version_number'],
|
||||
});
|
||||
});
|
||||
var packages = {};
|
||||
let packages = {};
|
||||
this.packages.forEach((package_name) => {
|
||||
packages[package_name] = true;
|
||||
});
|
||||
|
|
@ -118,7 +196,7 @@ class Home extends React.Component {
|
|||
|
||||
selectDevice = (device_name) => {
|
||||
if (device_name != null) {
|
||||
var device_id = this.deviceNamesID[device_name];
|
||||
const device_id = this.deviceNamesID[device_name];
|
||||
this.setState({
|
||||
showDeviceData: true,
|
||||
showSearch: false,
|
||||
|
|
@ -132,7 +210,7 @@ class Home extends React.Component {
|
|||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
search = (event) => {
|
||||
const query = event.target.value;
|
||||
|
|
@ -142,9 +220,9 @@ class Home extends React.Component {
|
|||
showSearch: false,
|
||||
});
|
||||
const deviceNames = this.fuzzySet.get(query, undefined, 0);
|
||||
var searchResults = [];
|
||||
let searchResults = [];
|
||||
if (deviceNames != null) {
|
||||
for (var i = 0; i < deviceNames.length && i < 6; i++) {
|
||||
for (let i = 0; i < deviceNames.length && i < 6; i++) {
|
||||
searchResults.push(deviceNames[i][1]);
|
||||
}
|
||||
}
|
||||
|
|
@ -152,202 +230,242 @@ class Home extends React.Component {
|
|||
searchResults,
|
||||
showSearch: query.length > 0,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
hideSearchResults = () => {
|
||||
console.log("bahar")
|
||||
this.setState({
|
||||
showSearch: false
|
||||
showSearch: false,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
changeInterface = (e, val) => {
|
||||
this.basicInterface = val;
|
||||
};
|
||||
|
||||
packageSettingChange = (event, package_name) => {
|
||||
var packages = this.state.packages;
|
||||
let packages = this.state.packages;
|
||||
packages[package_name] = event.target.checked;
|
||||
this.setState({
|
||||
packages,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
downlodingImageIndicatorShow = (i) => {
|
||||
downloadingImageIndicatorShow = (i) => {
|
||||
this.setState({
|
||||
downloading: true
|
||||
downloading: true,
|
||||
});
|
||||
setTimeout(() => {
|
||||
this.setState({
|
||||
downloading: false
|
||||
downloading: false,
|
||||
});
|
||||
}, 1000)
|
||||
}
|
||||
}, 1000);
|
||||
};
|
||||
|
||||
closeConfirmBuildDialog = (v) => {
|
||||
this.confirmingBuild = false;
|
||||
console.log(v);
|
||||
};
|
||||
|
||||
openConfirmBuildDialog = () => {
|
||||
this.confirmingBuild = true;
|
||||
};
|
||||
|
||||
render() {
|
||||
const warning432 = this.state.showDeviceData && parseInt((this.state.device['image_size'] || '').slice(0, -1)) <= 4000 && (
|
||||
<Paper className="warning-432" elevation={0}>
|
||||
<Grid
|
||||
container
|
||||
direction="row"
|
||||
justify="center"
|
||||
alignItems="center"
|
||||
>
|
||||
<Grid item>
|
||||
<WarningIcon className="icon" />
|
||||
</Grid>
|
||||
<Grid item xs>
|
||||
Devices with ≤4MB flash and/or ≤32MB ram will work but they will be very limited (usually they can't install or run additional packages) because they have low RAM and flash space. Consider this when choosing a device to buy, or when deciding to flash OpenWrt on your device because it is listed as supported.
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Paper>
|
||||
);
|
||||
const warning432 = this.state.showDeviceData &&
|
||||
parseInt(
|
||||
(this.state.device['image_size'] || '').slice(0, -1)) <= 4000 && (
|
||||
<Paper className="warning-432" elevation={0}>
|
||||
<Grid
|
||||
container
|
||||
direction="row"
|
||||
justify="center"
|
||||
alignItems="center"
|
||||
>
|
||||
<Grid item>
|
||||
<WarningIcon className="icon"/>
|
||||
</Grid>
|
||||
<Grid item xs>
|
||||
{this.props.t(
|
||||
'Devices with ≤4MB flash and/or ≤32MB ram will work but they will be very limited (usually they can\'t install or run additional packages) because they have low RAM and flash space. Consider this when choosing a device to buy, or when deciding to flash OpenWrt on your device because it is listed as supported.')}
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Paper>
|
||||
);
|
||||
const notLoaded = (
|
||||
<CircularProgress />
|
||||
<CircularProgress/>
|
||||
);
|
||||
const onLoad = (
|
||||
<>
|
||||
<Typography variant="h5">
|
||||
{this.props.t('Download OpenWrt firmware for your device!')}
|
||||
</Typography>
|
||||
<Typography>
|
||||
{this.props.t('Please use the input below to download firmware for your device!')}
|
||||
</Typography>
|
||||
<br />
|
||||
<ClickAwayListener onClickAway={this.hideSearchResults}>
|
||||
<div className="search-container">
|
||||
<FormControl fullWidth>
|
||||
<SearchTextField
|
||||
id="outlined-adornment-search-devices"
|
||||
labeltext={this.props.t('Search your device')}
|
||||
value={this.state.query}
|
||||
onChange={this.search}
|
||||
onClick={this.search}
|
||||
/>
|
||||
</FormControl>
|
||||
{
|
||||
this.state.showSearch && (
|
||||
<Paper elevation={4} className="search-results">
|
||||
<List>
|
||||
{
|
||||
this.state.searchResults.map((res, index) => {
|
||||
return (
|
||||
<ListItem
|
||||
key={res}
|
||||
button
|
||||
onClick={() => this.selectDevice(res)}
|
||||
>
|
||||
<ListItemText primary={
|
||||
<div>
|
||||
{res}
|
||||
</div>
|
||||
} />
|
||||
</ListItem>
|
||||
);
|
||||
})
|
||||
}
|
||||
</List>
|
||||
</Paper>
|
||||
)
|
||||
}
|
||||
{
|
||||
(this.state.searchResults.length === 0 && this.state.showSearch) && (
|
||||
<Paper elevation={4} className="search-results">
|
||||
<ListItem>
|
||||
<ListItemText primary={this.props.t('No results')}></ListItemText>
|
||||
</ListItem>
|
||||
</Paper>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
</ClickAwayListener>
|
||||
{
|
||||
this.state.showDeviceData && !this.state.deviceLoaded && (
|
||||
<>
|
||||
<br />
|
||||
{ notLoaded }
|
||||
</>
|
||||
)
|
||||
}
|
||||
{
|
||||
this.state.showDeviceData && this.state.deviceLoaded && (
|
||||
<>
|
||||
{ warning432 }
|
||||
<br />
|
||||
<Grid container className="device-info">
|
||||
<Grid item xs>
|
||||
<b>Name: </b> {this.state.device['title']}
|
||||
</Grid>
|
||||
<Grid item xs>
|
||||
<b>Target: </b> {this.state.device['target']}
|
||||
</Grid>
|
||||
<Grid item xs>
|
||||
<b>Subtarget: </b> {this.state.device['subtarget']}
|
||||
</Grid>
|
||||
<Grid item xs>
|
||||
<b>Image Size: </b> {this.state.device['image_size']}
|
||||
</Grid>
|
||||
</Grid>
|
||||
<ExpansionPanel elevation={0} className="advanced-settings">
|
||||
<ExpansionPanelSummary
|
||||
expandIcon={<ExpandMoreIcon />}
|
||||
aria-controls="panel1a-content"
|
||||
id="panel1a-header"
|
||||
>
|
||||
<Typography>Customize Packages (<WarningIcon className="icon" /> for advanced users)</Typography>
|
||||
</ExpansionPanelSummary>
|
||||
<ExpansionPanelDetails className="options">
|
||||
<FormGroup row>
|
||||
{
|
||||
this.packages.map((package_name, i) => {
|
||||
return (
|
||||
<FormControlLabel
|
||||
key={i}
|
||||
control={
|
||||
<Checkbox
|
||||
onChange={(event) => this.packageSettingChange(event, package_name)}
|
||||
value={this.state.packages[package_name]}
|
||||
checked={this.state.packages[package_name]}
|
||||
/>
|
||||
<>
|
||||
<Typography variant="h5">
|
||||
{this.props.t('Download OpenWrt firmware for your device!')}
|
||||
</Typography>
|
||||
<Typography>
|
||||
{this.props.t(
|
||||
'Please use the input below to download firmware for your device!')}
|
||||
</Typography>
|
||||
<br/>
|
||||
<ClickAwayListener onClickAway={this.hideSearchResults}>
|
||||
<div className="search-container">
|
||||
<FormControl fullWidth>
|
||||
<SearchTextField
|
||||
id="outlined-adornment-search-devices"
|
||||
labeltext={this.props.t('Search your device')}
|
||||
value={this.state.query}
|
||||
onChange={this.search}
|
||||
onClick={this.search}
|
||||
/>
|
||||
</FormControl>
|
||||
{
|
||||
this.state.showSearch && (
|
||||
<Paper elevation={4} className="search-results">
|
||||
<List>
|
||||
{
|
||||
this.state.searchResults.map((res, index) => {
|
||||
return (
|
||||
<ListItem
|
||||
key={res}
|
||||
button
|
||||
onClick={() => this.selectDevice(res)}
|
||||
>
|
||||
<ListItemText primary={
|
||||
<div>
|
||||
{res}
|
||||
</div>
|
||||
}/>
|
||||
</ListItem>
|
||||
);
|
||||
})
|
||||
}
|
||||
label={package_name}
|
||||
/>
|
||||
</List>
|
||||
</Paper>
|
||||
)
|
||||
}
|
||||
{
|
||||
(this.state.searchResults.length === 0 &&
|
||||
this.state.showSearch) && (
|
||||
<Paper elevation={4} className="search-results">
|
||||
<ListItem>
|
||||
<ListItemText
|
||||
primary={this.props.t('No results')}/>
|
||||
</ListItem>
|
||||
</Paper>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
</ClickAwayListener>
|
||||
{
|
||||
this.state.showDeviceData && !this.state.deviceLoaded && (
|
||||
<>
|
||||
<br/>
|
||||
{notLoaded}
|
||||
</>
|
||||
)
|
||||
}
|
||||
{
|
||||
this.state.showDeviceData && this.state.deviceLoaded && (
|
||||
<>
|
||||
{warning432}
|
||||
<br/>
|
||||
<Grid container className="device-info">
|
||||
<Grid item xs>
|
||||
<b>{this.props.t(
|
||||
'Name')}: </b> {this.state.device['title']}({this.state.device['target']}/{this.state.device['subtarget']})
|
||||
</Grid>
|
||||
<Grid item xs>
|
||||
<b>{this.props.t(
|
||||
'Release Version')}: </b> {this.state.release_version_number}
|
||||
</Grid>
|
||||
</Grid>
|
||||
<AppBar className="interface-switch-bar" position="relative"
|
||||
elevation={0}>
|
||||
<Tabs value={this.basicInterface}
|
||||
onChange={this.changeInterface}>
|
||||
<Tab className="interface-switch"
|
||||
label={this.props.t('Basic')}/>
|
||||
<Tab className="interface-switch"
|
||||
label={this.props.t('Advanced')}/>
|
||||
</Tabs>
|
||||
</AppBar>
|
||||
{
|
||||
this.basicInterface === 0 ? (
|
||||
<TabContainer>
|
||||
{
|
||||
this.state.device.images.map((image, i) => {
|
||||
return (
|
||||
<Button
|
||||
key={i}
|
||||
className="download-button"
|
||||
href={'http://downloads.openwrt.org/snapshots/targets/' +
|
||||
this.state.device.target + '/' +
|
||||
this.state.device.subtarget + '/' +
|
||||
image.name}
|
||||
color="primary"
|
||||
variant="contained"
|
||||
onClick={() => this.downloadingImageIndicatorShow()}
|
||||
>
|
||||
<CloudDownloadIcon
|
||||
className="download-icon"/>
|
||||
{image.type}
|
||||
</Button>
|
||||
);
|
||||
})
|
||||
}
|
||||
|
||||
{
|
||||
this.state.downloading && (
|
||||
<CircularProgress size={20}/>
|
||||
)
|
||||
}
|
||||
</TabContainer>
|
||||
) : (
|
||||
<TabContainer>
|
||||
<FormGroup row>
|
||||
{
|
||||
this.packages.map((package_name, i) => {
|
||||
return (
|
||||
<FormControlLabel
|
||||
className="package"
|
||||
key={i}
|
||||
control={
|
||||
<Checkbox
|
||||
onChange={(event) => this.packageSettingChange(
|
||||
event, package_name)}
|
||||
value={this.state.packages[package_name]}
|
||||
checked={this.state.packages[package_name]}
|
||||
/>
|
||||
}
|
||||
label={package_name}
|
||||
/>
|
||||
);
|
||||
})
|
||||
}
|
||||
</FormGroup>
|
||||
<br/>
|
||||
<Button variant="outlined" color="primary"
|
||||
onClick={this.openConfirmBuildDialog}>
|
||||
<BuildIcon/>
|
||||
|
||||
{this.props.t('Build')}
|
||||
</Button>
|
||||
</TabContainer>
|
||||
)
|
||||
})
|
||||
}
|
||||
</FormGroup>
|
||||
</ExpansionPanelDetails>
|
||||
</ExpansionPanel>
|
||||
{
|
||||
this.state.device.images.map((image, i) => {
|
||||
return (
|
||||
<Button
|
||||
key={i}
|
||||
className="download-button"
|
||||
href={"http://downloads.openwrt.org/snapshots/targets/" + this.state.device.target + "/" + this.state.device.subtarget + "/" + image.name}
|
||||
color="primary"
|
||||
variant="contained"
|
||||
onClick={() => this.downlodingImageIndicatorShow()}
|
||||
>
|
||||
<CloudDownloadIcon className="download-icon" />
|
||||
{ image.type }
|
||||
</Button>
|
||||
);
|
||||
})
|
||||
}
|
||||
|
||||
{
|
||||
this.state.downloading && (
|
||||
<CircularProgress size={20} />
|
||||
)
|
||||
}
|
||||
</>
|
||||
)
|
||||
}
|
||||
</>
|
||||
}
|
||||
</>
|
||||
)
|
||||
}
|
||||
</>
|
||||
);
|
||||
return (
|
||||
<Container className="home-container">
|
||||
<Paper className="home-container-paper">
|
||||
{ this.state.devicesLoaded ? onLoad : notLoaded }
|
||||
</Paper>
|
||||
</Container>
|
||||
<Container className="home-container">
|
||||
<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}
|
||||
</Paper>
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,68 +1,108 @@
|
|||
.home-container {
|
||||
margin-top: 30px;
|
||||
.home-container-paper {
|
||||
padding: 30px;
|
||||
text-align: left;
|
||||
.warning-432 {
|
||||
background-color: #f0f0f0;
|
||||
padding: 10px;
|
||||
margin-top: 20px;
|
||||
border: 1px solid #e3e3e3;
|
||||
color: #666;
|
||||
.icon {
|
||||
margin: 0 20px 0 10px;
|
||||
color: #F9A825
|
||||
}
|
||||
}
|
||||
.device-info {
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
.search-label {
|
||||
background-color: #fff;
|
||||
padding: 0 10px;
|
||||
position: absolute;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.search-results {
|
||||
position: absolute;
|
||||
z-index: 10;
|
||||
}
|
||||
.MuiTypography-h4 {
|
||||
font-weight: bold;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
.download-button {
|
||||
margin-right: 10px;
|
||||
.download-icon {
|
||||
margin-right: 10px;
|
||||
}
|
||||
}
|
||||
.advanced-settings {
|
||||
border: 1px solid #999;
|
||||
background-color: #f0f0f0;
|
||||
border-radius: 6px;
|
||||
margin-top: 20px;
|
||||
overflow: hidden;
|
||||
margin-bottom: 20px;
|
||||
.icon {
|
||||
font-size: 1em;
|
||||
vertical-align: text-top;
|
||||
}
|
||||
.options {
|
||||
padding-top: 30px;
|
||||
background-color: #fff;
|
||||
}
|
||||
}
|
||||
margin-top: 30px;
|
||||
|
||||
.home-container-paper {
|
||||
padding: 30px;
|
||||
text-align: left;
|
||||
|
||||
.warning-432 {
|
||||
background-color: #f0f0f0;
|
||||
padding: 10px;
|
||||
margin-top: 20px;
|
||||
border: 1px solid #e3e3e3;
|
||||
color: #666;
|
||||
|
||||
.icon {
|
||||
margin: 0 20px 0 10px;
|
||||
color: #F9A825
|
||||
}
|
||||
}
|
||||
|
||||
.device-info {
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.search-label {
|
||||
background-color: #fff;
|
||||
padding: 0 10px;
|
||||
position: absolute;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.search-results {
|
||||
position: absolute;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.MuiTypography-h4 {
|
||||
font-weight: bold;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.download-button {
|
||||
margin-right: 10px;
|
||||
|
||||
.download-icon {
|
||||
margin-right: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.interface-switch-bar {
|
||||
border: 1px solid #e3e3e3;
|
||||
border-radius: 4px;
|
||||
background-color: #fff;
|
||||
z-index: 9;
|
||||
overflow: hidden;
|
||||
.interface-switch {
|
||||
color: #000;
|
||||
}
|
||||
.advanced-settings {
|
||||
border: 1px solid #999;
|
||||
background-color: #f0f0f0;
|
||||
border-radius: 6px;
|
||||
margin-top: 20px;
|
||||
overflow: hidden;
|
||||
margin-bottom: 20px;
|
||||
|
||||
.icon {
|
||||
font-size: 1em;
|
||||
vertical-align: text-top;
|
||||
}
|
||||
|
||||
.options {
|
||||
padding-top: 30px;
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
.package {
|
||||
padding-right: 15px;
|
||||
padding-left: 5px;
|
||||
border-radius: 30px;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.package:hover {
|
||||
background-color: #f0f0f0;
|
||||
}
|
||||
|
||||
.package:focus, .package:active {
|
||||
transition: .2s;
|
||||
background-color: darken(#f0f0f0, 15%);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.device-table {
|
||||
margin-top: 20px;
|
||||
width: 100%;
|
||||
td {
|
||||
padding: 20px 30px;
|
||||
}
|
||||
tr:nth-child(odd) {
|
||||
background-color: rgba(104, 74, 238, 0.07);
|
||||
}
|
||||
margin-top: 20px;
|
||||
width: 100%;
|
||||
|
||||
td {
|
||||
padding: 20px 30px;
|
||||
}
|
||||
|
||||
tr:nth-child(odd) {
|
||||
background-color: rgba(104, 74, 238, 0.07);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import React from "react";
|
||||
import { Typography, Paper, Container } from "@material-ui/core";
|
||||
import { makeStyles } from '@material-ui/core/styles';
|
||||
import React from 'react';
|
||||
import {Container, Paper, Typography} from '@material-ui/core';
|
||||
import {makeStyles} from '@material-ui/core/styles';
|
||||
|
||||
const page404Styles = makeStyles(theme => ({
|
||||
root: {
|
||||
|
|
@ -8,19 +8,18 @@ const page404Styles = makeStyles(theme => ({
|
|||
},
|
||||
}));
|
||||
|
||||
|
||||
export default function NotFound() {
|
||||
var classes = page404Styles();
|
||||
return (
|
||||
<Container style={{marginTop: '50px'}}>
|
||||
<Paper className={classes.root} elevation={3}>
|
||||
<Typography variant="h5" component="h3">
|
||||
404 Page Not Found
|
||||
</Typography>
|
||||
<Typography component="p">
|
||||
Please head to the home.
|
||||
</Typography>
|
||||
</Paper>
|
||||
</Container>
|
||||
<Container style={{marginTop: '50px'}}>
|
||||
<Paper className={classes.root} elevation={3}>
|
||||
<Typography variant="h5" component="h3">
|
||||
404 Page Not Found
|
||||
</Typography>
|
||||
<Typography component="p">
|
||||
Please head to the home.
|
||||
</Typography>
|
||||
</Paper>
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue