Commit 658bdddb authored by Markus Scheidgen's avatar Markus Scheidgen
Browse files

Added upload cmd gui, partially.

parent 3842da5c
...@@ -10,6 +10,7 @@ ...@@ -10,6 +10,7 @@
"fetch": "^1.1.0", "fetch": "^1.1.0",
"html-to-react": "^1.3.3", "html-to-react": "^1.3.3",
"react": "^16.4.2", "react": "^16.4.2",
"react-copy-to-clipboard": "^5.0.1",
"react-dom": "^16.4.2", "react-dom": "^16.4.2",
"react-dropzone": "^5.0.1", "react-dropzone": "^5.0.1",
"react-highlight": "^0.12.0", "react-highlight": "^0.12.0",
......
...@@ -131,11 +131,16 @@ class Upload extends React.Component { ...@@ -131,11 +131,16 @@ class Upload extends React.Component {
return ( return (
<div className={classes.title}> <div className={classes.title}>
<Typography variant="title"> <Typography variant="title">
{name || upload_id} {name || new Date(Date.parse(create_time)).toLocaleString()}
</Typography>
<Typography variant="subheading">
{new Date(Date.parse(create_time)).toLocaleString()}
</Typography> </Typography>
{name
?
<Typography variant="subheading">
{new Date(Date.parse(create_time)).toLocaleString()}
</Typography>
:
'this upload has no name'
}
</div> </div>
) )
} }
......
...@@ -2,7 +2,14 @@ import React from 'react' ...@@ -2,7 +2,14 @@ import React from 'react'
import PropTypes from 'prop-types' import PropTypes from 'prop-types'
import Markdown from './Markdown' import Markdown from './Markdown'
import { withStyles, Paper, IconButton, FormGroup, Checkbox, FormControlLabel, FormLabel, import { withStyles, Paper, IconButton, FormGroup, Checkbox, FormControlLabel, FormLabel,
LinearProgress } from '@material-ui/core' LinearProgress,
FormControl,
InputLabel,
Input,
FormHelperText,
Button,
Popover,
Typography} from '@material-ui/core'
import UploadIcon from '@material-ui/icons/CloudUpload' import UploadIcon from '@material-ui/icons/CloudUpload'
import Dropzone from 'react-dropzone' import Dropzone from 'react-dropzone'
import api from '../api' import api from '../api'
...@@ -11,6 +18,7 @@ import { withErrors } from './errors' ...@@ -11,6 +18,7 @@ import { withErrors } from './errors'
import { compose } from 'recompose' import { compose } from 'recompose'
import DeleteIcon from '@material-ui/icons/Delete' import DeleteIcon from '@material-ui/icons/Delete'
import CheckIcon from '@material-ui/icons/Check' import CheckIcon from '@material-ui/icons/Check'
import AddIcon from '@material-ui/icons/Add'
import CommingSoon from './CommingSoon' import CommingSoon from './CommingSoon'
class Uploads extends React.Component { class Uploads extends React.Component {
...@@ -48,11 +56,31 @@ class Uploads extends React.Component { ...@@ -48,11 +56,31 @@ class Uploads extends React.Component {
}, },
uploads: { uploads: {
marginTop: theme.spacing.unit * 2 marginTop: theme.spacing.unit * 2
},
uploadFormControl: {
margin: theme.spacing.unit * 2,
},
button: {
margin: theme.spacing.unit,
},
rightIcon: {
marginLeft: theme.spacing.unit,
},
uploadNameInput: {
width: 300
},
uploadPopper: {
margin: theme.spacing.unit * 2
},
uploadCommand: {
fontFamily: 'Roboto mono, monospace',
marginTop: theme.spacing.unit * 2
} }
}) })
state = { state = {
uploads: null, selectedUploads: [], loading: true, acceptCommingSoon: false uploads: null, selectedUploads: [], loading: true, acceptCommingSoon: false,
uploadName: '', uploadCommand: null, showUploadCommand: false, uploadPopperAnchor: null
} }
componentDidMount() { componentDidMount() {
...@@ -72,6 +100,29 @@ class Uploads extends React.Component { ...@@ -72,6 +100,29 @@ class Uploads extends React.Component {
}) })
} }
onCreateUploadCmdClicked(event) {
const existingUpload = this.state.uploads.find(upload => upload.name === this.state.uploadName)
if (existingUpload) {
const upload = existingUpload
this.setState({
uploadCommand: upload.upload_command,
showUploadCommand: true,
uploadPopperAnchor: event.currentTarget})
} else {
api.createUpload(this.state.uploadName)
.then(upload => {
this.setState({
uploads: [...this.state.uploads, upload],
uploadCommand: upload.upload_command,
showUploadCommand: true,
uploadPopperAnchor: event.currentTarget})
})
.catch(error => {
this.props.raiseError(error)
})
}
}
onDeleteClicked() { onDeleteClicked() {
this.setState({loading: true}) this.setState({loading: true})
Promise.all(this.state.selectedUploads.map(upload => api.deleteUpload(upload.upload_id))) Promise.all(this.state.selectedUploads.map(upload => api.deleteUpload(upload.upload_id)))
...@@ -172,16 +223,19 @@ class Uploads extends React.Component { ...@@ -172,16 +223,19 @@ class Uploads extends React.Component {
render() { render() {
const { classes } = this.props const { classes } = this.props
const { showUploadCommand, uploadCommand, uploadPopperAnchor } = this.state
return ( return (
<div className={classes.root}> <div className={classes.root}>
<Markdown>{` <Markdown>{`
## Upload your own data ## Upload your own data
You can upload your own data. Have your code output ready in a popular archive You can upload your own data. Have your code output ready in a popular archive
format (e.g. \`*.zip\` or \`*.tar.gz\`) and drop it below. Your upload can format (e.g. \`*.zip\` or \`*.tar.gz\`). Your upload can
comprise the output of multiple runs, even of different codes. Don't worry, nomad comprise the output of multiple runs, even of different codes. Don't worry, nomad
will find it. will find it.
### Browser upload
Just drop your file below.
`}</Markdown> `}</Markdown>
<Paper> <Paper>
<Dropzone <Dropzone
...@@ -195,6 +249,49 @@ class Uploads extends React.Component { ...@@ -195,6 +249,49 @@ class Uploads extends React.Component {
<UploadIcon style={{fontSize: 36}}/> <UploadIcon style={{fontSize: 36}}/>
</Dropzone> </Dropzone>
</Paper> </Paper>
<Markdown>{`
### Command line upload
Alternatively, you can upload your file via \`curl\`. The name is
optional, but it will help you to track your uploads.
`}</Markdown>
<Paper>
<FormControl className={classes.uploadFormControl}>
<InputLabel htmlFor="name-helper">Upload name</InputLabel>
<Input className={classes.uploadNameInput}
id="name-helper" value={this.state.uploadName}
onChange={(event) => this.setState({uploadName: event.target.value})}
/>
<FormHelperText id="name-helper-text">With out name, you only see the time</FormHelperText>
</FormControl>
<FormControl className={classes.uploadFormControl}>
<Button
color="primary" className={classes.button} variant="contained"
onClick={this.onCreateUploadCmdClicked.bind(this)}
>
add upload
<AddIcon className={classes.rightIcon}/>
</Button>
<Popover
id="upload-command-popper"
onClose={() => this.setState({showUploadCommand: false})}
open={showUploadCommand}
anchorEl={uploadPopperAnchor}
anchorOrigin={{
vertical: 'bottom',
horizontal: 'center',
}}
transformOrigin={{
vertical: 'top',
horizontal: 'center',
}}
>
<div className={classes.uploadPopper}>
<Typography>Copy and use the following command. Don't forget to replace the file name.:</Typography>
<Typography className={classes.uploadCommand}>{uploadCommand}</Typography>
</div>
</Popover>
</FormControl>
</Paper>
{this.renderUploads()} {this.renderUploads()}
{this.state.loading ? <LinearProgress/> : ''} {this.state.loading ? <LinearProgress/> : ''}
......
...@@ -1898,6 +1898,12 @@ copy-descriptor@^0.1.0: ...@@ -1898,6 +1898,12 @@ copy-descriptor@^0.1.0:
version "0.1.1" version "0.1.1"
resolved "https://registry.yarnpkg.com/copy-descriptor/-/copy-descriptor-0.1.1.tgz#676f6eb3c39997c2ee1ac3a924fd6124748f578d" resolved "https://registry.yarnpkg.com/copy-descriptor/-/copy-descriptor-0.1.1.tgz#676f6eb3c39997c2ee1ac3a924fd6124748f578d"
copy-to-clipboard@^3:
version "3.0.8"
resolved "https://registry.yarnpkg.com/copy-to-clipboard/-/copy-to-clipboard-3.0.8.tgz#f4e82f4a8830dce4666b7eb8ded0c9bcc313aba9"
dependencies:
toggle-selection "^1.0.3"
core-js@^1.0.0: core-js@^1.0.0:
version "1.2.7" version "1.2.7"
resolved "https://registry.yarnpkg.com/core-js/-/core-js-1.2.7.tgz#652294c14651db28fa93bd2d5ff2983a4f08c636" resolved "https://registry.yarnpkg.com/core-js/-/core-js-1.2.7.tgz#652294c14651db28fa93bd2d5ff2983a4f08c636"
...@@ -6156,6 +6162,13 @@ react-base16-styling@^0.6.0: ...@@ -6156,6 +6162,13 @@ react-base16-styling@^0.6.0:
lodash.flow "^3.3.0" lodash.flow "^3.3.0"
pure-color "^1.2.0" pure-color "^1.2.0"
react-copy-to-clipboard@^5.0.1:
version "5.0.1"
resolved "https://registry.yarnpkg.com/react-copy-to-clipboard/-/react-copy-to-clipboard-5.0.1.tgz#8eae107bb400be73132ed3b6a7b4fb156090208e"
dependencies:
copy-to-clipboard "^3"
prop-types "^15.5.8"
react-dev-utils@^5.0.1: react-dev-utils@^5.0.1:
version "5.0.1" version "5.0.1"
resolved "https://registry.yarnpkg.com/react-dev-utils/-/react-dev-utils-5.0.1.tgz#1f396e161fe44b595db1b186a40067289bf06613" resolved "https://registry.yarnpkg.com/react-dev-utils/-/react-dev-utils-5.0.1.tgz#1f396e161fe44b595db1b186a40067289bf06613"
...@@ -7385,6 +7398,10 @@ to-regex@^3.0.1, to-regex@^3.0.2: ...@@ -7385,6 +7398,10 @@ to-regex@^3.0.1, to-regex@^3.0.2:
regex-not "^1.0.2" regex-not "^1.0.2"
safe-regex "^1.1.0" safe-regex "^1.1.0"
toggle-selection@^1.0.3:
version "1.0.6"
resolved "https://registry.yarnpkg.com/toggle-selection/-/toggle-selection-1.0.6.tgz#6e45b1263f2017fa0acc7d89d78b15b8bf77da32"
toposort@^1.0.0: toposort@^1.0.0:
version "1.0.7" version "1.0.7"
resolved "https://registry.yarnpkg.com/toposort/-/toposort-1.0.7.tgz#2e68442d9f64ec720b8cc89e6443ac6caa950029" resolved "https://registry.yarnpkg.com/toposort/-/toposort-1.0.7.tgz#2e68442d9f64ec720b8cc89e6443ac6caa950029"
......
...@@ -106,8 +106,7 @@ def create_curl_upload_cmd(presigned_url: str, file_dummy: str='<ZIPFILE>') -> s ...@@ -106,8 +106,7 @@ def create_curl_upload_cmd(presigned_url: str, file_dummy: str='<ZIPFILE>') -> s
Returns: Returns:
The curl shell command with correct method, url, headers, etc. The curl shell command with correct method, url, headers, etc.
""" """
headers = 'Content-Type: application/octet-steam' return 'curl "%s" --upload-file %s' % (presigned_url, file_dummy)
return 'curl -X PUT "%s" -H "%s" -F file=@%s' % (presigned_url, headers, file_dummy)
def upload_put_handler(func: Callable[[str], None]) -> Callable[[], None]: def upload_put_handler(func: Callable[[str], None]) -> Callable[[], None]:
......
...@@ -202,6 +202,7 @@ class Upload(Proc): ...@@ -202,6 +202,7 @@ class Upload(Proc):
is_private = BooleanField(default=False) is_private = BooleanField(default=False)
presigned_url = StringField() presigned_url = StringField()
upload_command = StringField()
upload_time = DateTimeField() upload_time = DateTimeField()
upload_hash = StringField(default=None) upload_hash = StringField(default=None)
...@@ -268,6 +269,7 @@ class Upload(Proc): ...@@ -268,6 +269,7 @@ class Upload(Proc):
""" """
self = super().create(**kwargs) self = super().create(**kwargs)
self.presigned_url = files.get_presigned_upload_url(self.upload_id) self.presigned_url = files.get_presigned_upload_url(self.upload_id)
self.upload_command = files.create_curl_upload_cmd(self.presigned_url, 'your_file')
self._continue_with('uploading') self._continue_with('uploading')
return self return self
...@@ -285,7 +287,8 @@ class Upload(Proc): ...@@ -285,7 +287,8 @@ class Upload(Proc):
'name': self.name, 'name': self.name,
'additional_metadata': self.additional_metadata, 'additional_metadata': self.additional_metadata,
'upload_id': self.upload_id, 'upload_id': self.upload_id,
'presigned_url': files.external_objects_url(self.presigned_url), 'presigned_url': self.presigned_url,
'upload_command': self.upload_command,
'upload_time': self.upload_time.isoformat() if self.upload_time is not None else None, 'upload_time': self.upload_time.isoformat() if self.upload_time is not None else None,
'is_stale': self.is_stale, 'is_stale': self.is_stale,
} }
......
...@@ -55,6 +55,7 @@ def assert_upload(upload_json_str, id=None, **kwargs): ...@@ -55,6 +55,7 @@ def assert_upload(upload_json_str, id=None, **kwargs):
assert id == data['upload_id'] assert id == data['upload_id']
assert 'create_time' in data assert 'create_time' in data
assert 'presigned_url' in data assert 'presigned_url' in data
assert 'upload_command' in data
for key, value in kwargs.items(): for key, value in kwargs.items():
assert data.get(key, None) == value assert data.get(key, None) == value
......
...@@ -108,7 +108,7 @@ def test_presigned_url(upload_id): ...@@ -108,7 +108,7 @@ def test_presigned_url(upload_id):
subprocess.call(shlex.split(cmd)) subprocess.call(shlex.split(cmd))
stat = files._client.stat_object(config.files.uploads_bucket, upload_id) stat = files._client.stat_object(config.files.uploads_bucket, upload_id)
assert stat.content_type.startswith('application/octet-steam') assert stat is not None
def test_upload(uploaded_id: str): def test_upload(uploaded_id: str):
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment