Commit 278fb1f7 authored by Markus Scheidgen's avatar Markus Scheidgen
Browse files

Updated the GUI for the latest API changes.

parent 2674de0e
......@@ -20,6 +20,7 @@
"react-router-hash-link": "^1.2.0",
"react-scripts": "1.1.4",
"recompose": "^0.28.2",
"swagger-client": "^3.8.22",
"url-parse": "^1.4.3"
},
"scripts": {
......
import { UploadRequest } from '@navjobs/upload'
import Swagger from 'swagger-client'
import { apiBase, appStaticBase } from './config'
const auth_headers = {
Authorization: 'Basic ' + btoa('sheldon.cooper@nomad-fairdi.tests.de:password')
}
const networkError = () => {
throw Error('Network related error, cannot reach API or object storage.')
const swaggerPromise = Swagger(`${apiBase}/swagger.json`, {
authorizations: {
// my_query_auth: new ApiKeyAuthorization('my-query', 'bar', 'query'),
// my_header_auth: new ApiKeyAuthorization('My-Header', 'bar', 'header'),
'HTTP Basic': {
username: 'sheldon.cooper@nomad-fairdi.tests.de',
password: 'password'
}
// cookie_: new CookieAuthorization('one=two')
}
})
const networkError = (e) => {
throw Error('Network related error, cannot reach API: ' + e)
}
const handleJsonErrors = () => {
......@@ -27,19 +40,16 @@ const handleResponseErrors = (response) => {
class Upload {
constructor(json, created) {
this.uploading = null
this.uploading = 0
this._assignFromJson(json, created)
}
uploadFile(file) {
console.assert(this.upload_url)
this.uploading = 0
const uploadFileWithProgress = async() => {
let { error, aborted } = await UploadRequest(
let uploadRequest = await UploadRequest(
{
request: {
url: this.upload_url,
url: `${apiBase}/uploads/?name=${this.name}`,
method: 'PUT',
headers: {
'Content-Type': 'application/gzip',
......@@ -53,12 +63,14 @@ class Upload {
}
}
)
if (error) {
networkError(error)
if (uploadRequest.error) {
networkError(uploadRequest.error)
}
if (aborted) {
if (uploadRequest.aborted) {
throw Error('User abort')
}
this.uploading = 100
this.upload_id = uploadRequest.response.upload_id
}
return uploadFileWithProgress()
......@@ -70,9 +82,7 @@ class Upload {
if (this.current_task !== this.tasks[0]) {
this.uploading = 100
this.waiting = false
} else if (!created && this.uploading === null) {
// if data came from server during a normal get (not create) and its still uploading
// and the uploading is also not controlled locally then it ought to be a manual upload
} else {
this.waiting = true
}
}
......@@ -107,26 +117,16 @@ class Upload {
}
function createUpload(name) {
const fetchData = {
method: 'POST',
body: JSON.stringify({
name: name
}),
headers: {
'Content-Type': 'application/json',
...auth_headers
}
}
return fetch(`${apiBase}/uploads`, fetchData)
.catch(networkError)
.then(handleResponseErrors)
.then(response => response.json())
.then(uploadJson => new Upload(uploadJson, true))
return new Upload({
name: name,
tasks: ['UPLOADING'],
current_task: 'UPLOADING'
}, true)
}
function getUploads() {
return fetch(
`${apiBase}/uploads`,
`${apiBase}/uploads/`,
{
method: 'GET',
headers: auth_headers
......@@ -145,7 +145,7 @@ function archive(uploadHash, calcHash) {
}
function calcProcLog(archiveId) {
return fetch(`${apiBase}/logs/${archiveId}`)
return fetch(`${apiBase}/archive/logs/${archiveId}`)
.catch(networkError)
.then(response => {
if (!response.ok) {
......@@ -173,7 +173,7 @@ function repo(uploadHash, calcHash) {
function repoAll(page, perPage, owner) {
return fetch(
`${apiBase}/repo?page=${page}&per_page=${perPage}&owner=${owner || 'all'}`,
`${apiBase}/repo/?page=${page}&per_page=${perPage}&owner=${owner || 'all'}`,
{
method: 'GET',
headers: auth_headers
......@@ -246,7 +246,16 @@ async function getMetaInfo() {
}
}
async function getUploadCommand() {
const client = await swaggerPromise
return client.apis.uploads.get_upload_command()
.catch(networkError)
.then(handleResponseErrors)
.then(response => response.body.upload_command)
}
const api = {
getUploadCommand: getUploadCommand,
createUpload: createUpload,
deleteUpload: deleteUpload,
unstageUpload: unstageUpload,
......
......@@ -108,7 +108,7 @@ class ArchiveCalc extends React.Component {
see a *meta-info* description.
`}</Markdown>
<Typography className={classes.logLink}>
The processing logs are available <a href="#" onClick={() => this.setState({showLogs: true})}>here</a>.
The processing logs are available <a href="#logs" onClick={() => this.setState({showLogs: true})}>here</a>.
</Typography>
<Paper className={classes.calcData}>
{
......
......@@ -25,7 +25,7 @@ class Documentation extends Component {
return (
<div className={classes.root}>
<div className={classes.content}>
<iframe
<iframe title="documentation"
frameBorder={0} width="700" height={window.innerHeight - 64}
src={`${apiBase}/docs/index.html`}
/>
......
......@@ -12,7 +12,6 @@ import CalcLinks from './CalcLinks'
import { compose } from 'recompose'
import { withErrors } from './errors'
import { debug } from '../config'
import UploadCommand from './UploadCommand'
import CalcProcLogPopper from './CalcProcLogPopper'
class Upload extends React.Component {
......@@ -265,7 +264,7 @@ class Upload extends React.Component {
renderCalcTable() {
const { classes } = this.props
const { page, perPage, orderBy, order } = this.state.params
const { calcs, status, waiting, upload_command } = this.state.upload
const { calcs, status, waiting } = this.state.upload
const { pagination, results } = calcs
if (pagination.total === 0) {
......@@ -278,7 +277,9 @@ class Upload extends React.Component {
} else {
if (waiting) {
return (
<UploadCommand uploadCommand={upload_command} />
<Typography className={classes.detailsContent}>
Uploading ...
</Typography>
)
} else {
return (
......@@ -323,7 +324,7 @@ class Upload extends React.Component {
<Typography color={color}>
{(status === 'SUCCESS' || status === 'FAILURE')
?
<a className={classes.logLink} href="#" onClick={() => this.setState({archiveLogs: archive_id})}>
<a className={classes.logLink} href="#logs" onClick={() => this.setState({archiveLogs: archive_id})}>
{status.toLowerCase()}
</a>
: status.toLowerCase()
......
import React from 'react'
import PropTypes from 'prop-types'
import { Typography, withStyles } from '@material-ui/core'
class UploadCommand extends React.Component {
static propTypes = {
classes: PropTypes.object.isRequired,
uploadCommand: PropTypes.string.isRequired
}
static styles = theme => ({
root: {
margin: theme.spacing.unit * 2
},
uploadCommand: {
fontFamily: '\'Roboto mono\', monospace',
marginTop: theme.spacing.unit * 2
}
})
render() {
const { classes, uploadCommand } = this.props
return (
<div className={classes.root}>
<Typography>Copy and use the following command. Don't forget to replace the file name.:</Typography>
<Typography className={classes.uploadCommand}>{uploadCommand}</Typography>
</div>
)
}
}
export default withStyles(UploadCommand.styles)(UploadCommand)
......@@ -2,8 +2,7 @@ import React from 'react'
import PropTypes from 'prop-types'
import Markdown from './Markdown'
import { withStyles, Paper, IconButton, FormGroup, Checkbox, FormControlLabel, FormLabel,
LinearProgress, InputLabel, Input, FormHelperText, Button, Popover, Grid, Typography,
DialogContent, DialogActions} from '@material-ui/core'
LinearProgress } from '@material-ui/core'
import UploadIcon from '@material-ui/icons/CloudUpload'
import Dropzone from 'react-dropzone'
import api from '../api'
......@@ -12,8 +11,6 @@ import { withErrors } from './errors'
import { compose } from 'recompose'
import DeleteIcon from '@material-ui/icons/Delete'
import CheckIcon from '@material-ui/icons/Check'
import AddIcon from '@material-ui/icons/Add'
import UploadCommand from './UploadCommand'
import ConfirmDialog from './ConfirmDialog'
class Uploads extends React.Component {
......@@ -62,44 +59,24 @@ class Uploads extends React.Component {
},
uploads: {
marginTop: theme.spacing.unit * 2
},
uploadFormControl: {
margin: theme.spacing.unit * 2
},
button: {
margin: theme.spacing.unit
},
rightIcon: {
marginLeft: theme.spacing.unit
},
uploadNameInput: {
width: '100%'
},
uploadKindHeading: {
paddingBottom: theme.spacing.unit
},
uploadKindDescription: {
paddingTop: theme.spacing.unit,
paddingBottom: theme.spacing.unit * 2
},
commandUpload: {
height: 192
}
})
state = {
uploads: null,
uploadCommand: null,
selectedUploads: [],
loading: true,
showAccept: false,
uploadName: '',
uploadCommand: null,
showUploadCommand: false,
uploadPopperAnchor: null
showAccept: false
}
componentDidMount() {
this.update()
api.getUploadCommand()
.then(command => this.setState({uploadCommand: command}))
.catch(error => {
this.props.raiseError(error)
})
}
update() {
......@@ -115,31 +92,6 @@ class Uploads extends React.Component {
})
}
onCreateUploadCmdClicked(event) {
event.persist()
const existingUpload = this.state.uploads
.find(upload => upload.name === this.state.uploadName && upload.waiting)
if (existingUpload) {
const upload = existingUpload
this.setState({
uploadCommand: upload.upload_command,
showUploadCommand: true,
uploadPopperAnchor: event.target})
} else {
api.createUpload(this.state.uploadName)
.then(upload => {
this.setState({
uploads: [...this.state.uploads, upload],
uploadCommand: upload.upload_command,
showUploadCommand: true,
uploadPopperAnchor: event.target})
})
.catch(error => {
this.props.raiseError(error)
})
}
}
onDeleteClicked() {
this.setState({loading: true})
Promise.all(this.state.selectedUploads.map(upload => api.deleteUpload(upload.upload_id)))
......@@ -169,13 +121,9 @@ class Uploads extends React.Component {
onDrop(files) {
files.forEach(file => {
api.createUpload(file.name)
.then(upload => {
this.setState({uploads: [...this.state.uploads, upload]})
upload.uploadFile(file)
.catch(this.props.raiseError)
})
.catch(this.props.raiseError)
const upload = api.createUpload(file.name)
this.setState({uploads: [...this.state.uploads, upload]})
upload.uploadFile(file).catch(this.props.raiseError)
})
}
......@@ -248,7 +196,7 @@ class Uploads extends React.Component {
render() {
const { classes } = this.props
const { showUploadCommand, uploadCommand, uploadPopperAnchor } = this.state
const { uploadCommand } = this.state
return (
<div className={classes.root}>
......@@ -257,76 +205,32 @@ class Uploads extends React.Component {
You can upload your own data. Have your code output ready in a popular archive
format (e.g. \`*.zip\` or \`*.tar.gz\`). Your upload can
comprise the output of multiple runs, even of different codes. Don't worry, nomad
will find it.`}
will find it, just drop it below:`}
</Markdown>
<Paper className={classes.dropzoneContainer}>
<Dropzone
accept="application/zip"
className={classes.dropzone}
activeClassName={classes.dropzoneAccept}
rejectClassName={classes.dropzoneReject}
onDrop={this.onDrop.bind(this)}
>
<p>drop files here</p>
<UploadIcon style={{fontSize: 36}}/>
</Dropzone>
</Paper>
<Markdown>{`
Alternatively, you can upload files via the following shell command.
Replace \`<local_file>\` with your file. After executing the command,
return here and reload.
\`\`\`
${uploadCommand}
\`\`\`
`}
</Markdown>
<Grid container spacing={24}>
<Grid item xs>
<Typography variant="headline" className={classes.uploadKindHeading}>
Browser upload
</Typography>
<Paper className={classes.dropzoneContainer}>
<Dropzone
accept="application/zip"
className={classes.dropzone}
activeClassName={classes.dropzoneAccept}
rejectClassName={classes.dropzoneReject}
onDrop={this.onDrop.bind(this)}
>
<p>drop files here</p>
<UploadIcon style={{fontSize: 36}}/>
</Dropzone>
</Paper>
<Typography className={classes.uploadKindDescription}>
Just drop your file above. You know this from many other services in the internet.
</Typography>
</Grid>
<Grid item xs>
<Typography variant="headline" className={classes.uploadKindHeading}>
Command upload
</Typography>
<Paper className={classes.commandUpload}>
<DialogContent>
<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">optional, helps to track the upload</FormHelperText>
</DialogContent>
<DialogActions>
<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'
}}
>
<UploadCommand uploadCommand={uploadCommand} />
</Popover>
</DialogActions>
</Paper>
<Typography className={classes.uploadKindDescription}>
You can upload your file via <strong>curl</strong>. Optionally, you
can provide a name that will help to track different uploads.
Without a name, you only have the upload time to follow your uploads.
You can find the command by unfolding the new upload element.
</Typography>
</Grid>
</Grid>
{this.renderUploads()}
{this.state.loading ? <LinearProgress/> : ''}
......
......@@ -90,6 +90,16 @@
lodash "^4.2.0"
to-fast-properties "^2.0.0"
"@kyleshockey/js-yaml@^1.0.1":
version "1.0.1"
resolved "https://registry.yarnpkg.com/@kyleshockey/js-yaml/-/js-yaml-1.0.1.tgz#5c036bb67caee77fa887738e695dc02949889bfd"
dependencies:
argparse "^1.0.7"
"@kyleshockey/object-assign-deep@^0.4.0":
version "0.4.2"
resolved "https://registry.yarnpkg.com/@kyleshockey/object-assign-deep/-/object-assign-deep-0.4.2.tgz#84900f0eefc372798f4751b5262830b8208922ec"
"@material-ui/core@^1.5.1":
version "1.5.1"
resolved "https://registry.yarnpkg.com/@material-ui/core/-/core-1.5.1.tgz#cb00cb934447ae688e08129f1dab55f54d29d87a"
......@@ -474,7 +484,7 @@ async@^1.4.0, async@^1.5.2:
version "1.5.2"
resolved "https://registry.yarnpkg.com/async/-/async-1.5.2.tgz#ec6a61ae56480c0c3cb241c95618e20892f9672a"
async@^2.1.2, async@^2.1.4, async@^2.4.1:
async@^2.0.1, async@^2.1.2, async@^2.1.4, async@^2.4.1:
version "2.6.1"
resolved "https://registry.yarnpkg.com/async/-/async-2.6.1.tgz#b245a23ca71930044ec53fa46aa00a3e87c6a610"
dependencies:
......@@ -1434,6 +1444,10 @@ bser@^2.0.0:
dependencies:
node-int64 "^0.4.0"
btoa@1.1.2:
version "1.1.2"
resolved "https://registry.yarnpkg.com/btoa/-/btoa-1.1.2.tgz#3e40b81663f81d2dd6596a4cb714a8dc16cfabe0"
buffer-from@^1.0.0:
version "1.1.1"
resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.1.tgz#32713bc028f75c02fdb710d7c7bcec1f2c6070ef"
......@@ -1454,6 +1468,13 @@ buffer@^4.3.0:
ieee754 "^1.1.4"
isarray "^1.0.0"
buffer@^5.1.0:
version "5.2.1"
resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.2.1.tgz#dd57fa0f109ac59c602479044dca7b8b3d0b71d6"
dependencies:
base64-js "^1.0.2"
ieee754 "^1.1.4"
builtin-modules@^1.0.0, builtin-modules@^1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-1.1.1.tgz#270f076c5a72c02f5b65a47df94c5fe3a278892f"
......@@ -1784,6 +1805,12 @@ combined-stream@1.0.6, combined-stream@~1.0.6:
dependencies:
delayed-stream "~1.0.0"
combined-stream@^1.0.5:
version "1.0.7"
resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.7.tgz#2d1d24317afb8abe95d6d2c0b07b57813539d828"
dependencies:
delayed-stream "~1.0.0"
commander@2.17.x, commander@^2.11.0:
version "2.17.1"
resolved "https://registry.yarnpkg.com/commander/-/commander-2.17.1.tgz#bd77ab7de6de94205ceacc72f1716d29f20a77bf"
......@@ -1894,7 +1921,7 @@ cookie-signature@1.0.6:
version "1.0.6"
resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c"
cookie@0.3.1:
cookie@0.3.1, cookie@^0.3.1:
version "0.3.1"
resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.3.1.tgz#e7e0a1f9ef43b4c8ba925c5c5a96e806d16873bb"
......@@ -1966,6 +1993,13 @@ create-hmac@^1.1.0, create-hmac@^1.1.2, create-hmac@^1.1.4:
safe-buffer "^5.0.1"
sha.js "^2.4.8"
cross-fetch@0.0.8:
version "0.0.8"
resolved "https://registry.yarnpkg.com/cross-fetch/-/cross-fetch-0.0.8.tgz#01ed94dc407df2c00f1807fde700a7cfa48a205c"
dependencies:
node-fetch "1.7.3"
whatwg-fetch "2.0.3"
cross-spawn@5.1.0, cross-spawn@^5.0.1, cross-spawn@^5.1.0:
version "5.1.0"
resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-5.1.0.tgz#e8bd0efee58fcff6f8f94510a0a554bbfa235449"
......@@ -2160,6 +2194,10 @@ deep-equal@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/deep-equal/-/deep-equal-1.0.1.tgz#f5d260292b660e084eff4cdbc9f08ad3247448b5"
deep-extend@^0.5.1:
version "0.5.1"
resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.5.1.tgz#b894a9dd90d3023fbf1c55a394fb858eb2066f1f"
deep-extend@^0.6.0:
version "0.6.0"
resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.6.0.tgz#c4fa7c95404a17a9c3e8ca7e1537312b736330ac"
......@@ -2448,6 +2486,10 @@ emojis-list@^2.0.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/emojis-list/-/emojis-list-2.1.0.tgz#4daa4d9db00f9819880c79fa457ae5b09a1fd389"
encode-3986@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/encode-3986/-/encode-3986-1.0.0.tgz#940d51498f8741ade184b75ad1439b317c0c7a60"
encodeurl@~1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59"
......@@ -3031,6 +3073,12 @@ fast-deep-equal@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz#7b05218ddf9667bf7f370bf7fdb2cb15fdd0aa49"
fast-json-patch@^2.0.6:
version "2.0.7"
resolved "https://registry.yarnpkg.com/fast-json-patch/-/fast-json-patch-2.0.7.tgz#55864b08b1e50381d2f37fd472bb2e18fe54a733"
dependencies:
deep-equal "^1.0.1"
fast-json-stable-stringify@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz#d5142c0caee6b1189f87d3a76111064f86c8bbf2"
......@@ -3233,6 +3281,14 @@ forever-agent@~0.6.1:
version "0.6.1"
resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91"
form-data@^1.0.0-rc3:
version "1.0.1"
resolved "http://registry.npmjs.org/form-data/-/form-data-1.0.1.tgz#ae315db9a4907fa065502304a66d7733475ee37c"
dependencies:
async "^2.0.1"
combined-stream "^1.0.5"
mime-types "^2.1.11"
form-data@~2.3.2:
version "2.3.2"
resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.3.2.tgz#4970498be604c20c005d4f5c23aecd21d6b49099"
......@@ -4170,6 +4226,12 @@ isomorphic-fetch@^2.1.1:
node-fetch "^1.0.1"
whatwg-fetch ">=0.10.0"
isomorphic-form-data@0.0.1:
version "0.0.1"
resolved "https://registry.yarnpkg.com/isomorphic-form-data/-/isomorphic-form-data-0.0.1.tgz#026f627e032b0cd8413ecc8755928b94a468b062"
dependencies:
form-data "^1.0.0-rc3"
isstream@~0.1.2:
version "0.1.2"
resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a"
......@@ -4841,6 +4903,10 @@ lodash.uniq@^4.5.0:
version "4.17.10"
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.10.tgz#1b7793cf7259ea38fb3661d4d38b3260af8ae4e7"
lodash@^4.16.2:
version "4.17.11"