@@ -4,4 +4,4 @@ node_modules | |||
package-lock.json | |||
/frontend/dist/ | |||
/backend/server.pem | |||
/testdata/ |
@@ -16,7 +16,7 @@ export default { | |||
}, | |||
plugins: [ | |||
includePaths({ | |||
paths: ['src/js', 'src/js/widget', 'src/js/misc', 'src/js/component', 'src/js/page'] | |||
paths: ['src/js', 'src/js/widget', 'src/js/misc', 'src/js/component', 'src/js/page', 'src/js/dialog'] | |||
}), | |||
copy({ | |||
'src/html/index.html': 'dist/index.html', | |||
@@ -21,6 +21,8 @@ class WBCollectionListing extends Component { | |||
} | |||
prepareRows(items, ownerLookup) { | |||
let { app } = this.props; | |||
return items.map(item => [ | |||
(<div> | |||
<div> | |||
@@ -42,10 +44,16 @@ class WBCollectionListing extends Component { | |||
item['file_count'], | |||
filesize(item['file_size_total']), | |||
(<div> | |||
<button class="btn btn-outline-warning m-1" title="Add to Toolbox" | |||
onclick={ () => (app.addToToolbox(item.uuid)) }> | |||
<i class="fas fa-toolbox"></i> | |||
</button> | |||
<a class="btn btn-outline-primary m-1" title="Properties" | |||
href={ urlForObject(item, 'properties') }> | |||
<i class="fas fa-list-ul"></i> | |||
</a> | |||
<button class="btn btn-outline-danger m-1" title="Delete"> | |||
<i class="fas fa-trash"></i> | |||
</button> | |||
@@ -17,6 +17,8 @@ class WBProjectListing extends Component { | |||
} | |||
prepareRows(items) { | |||
let { app } = this.props; | |||
return items.map(item => [ | |||
(<div> | |||
<div> | |||
@@ -26,7 +28,13 @@ class WBProjectListing extends Component { | |||
<div>{ item['uuid'] }</div> | |||
</div>), | |||
item['description'], | |||
item['owner_uuid'] | |||
item['owner_uuid'], | |||
(<div> | |||
<button class="btn btn-outline-warning m-1" title="Add to Toolbox" | |||
onclick={ () => (app.addToToolbox(item.uuid)) }> | |||
<i class="fas fa-toolbox"></i> | |||
</button> | |||
</div>) | |||
]); | |||
} | |||
@@ -57,8 +65,9 @@ class WBProjectListing extends Component { | |||
render({ arvHost, arvToken, ownerUuid, activePage, onPageChanged }, { rows, numPages }) { | |||
return ( | |||
<div> | |||
<WBTable columns={ [ 'Name', 'Description', 'Owner' ] } | |||
<WBTable columns={ [ 'Name', 'Description', 'Owner', 'Actions' ] } | |||
rows={ rows } /> | |||
<WBPagination numPages={ numPages } | |||
activePage={ activePage } | |||
onPageChanged={ i => onPageChanged(i) } /> | |||
@@ -5,6 +5,7 @@ import WBPagination from 'wb-pagination'; | |||
import WBNameAndUuid from 'wb-name-and-uuid'; | |||
import wbFetchObjects from 'wb-fetch-objects'; | |||
import wbFormatDate from 'wb-format-date'; | |||
import urlForObject from 'url-for-object'; | |||
class WBWorkflowListing extends Component { | |||
@@ -25,7 +26,8 @@ class WBWorkflowListing extends Component { | |||
( <WBNameAndUuid uuid={ item.owner_uuid } lookup={ ownerLookup } /> ), | |||
wbFormatDate(item.created_at), | |||
(<div> | |||
<button class="btn btn-outline-success mx-1 my-1" title="Run"><i class="fas fa-running"></i></button> | |||
<a class="btn btn-outline-success mx-1 my-1" title="Launch" | |||
href={ urlForObject(item, 'launch') }><i class="fas fa-running"></i></a> | |||
<button class="btn btn-outline-primary mx-1 my-1" title="View"><i class="far fa-eye"></i></button> | |||
<button class="btn btn-outline-danger mx-1 my-1" title="Delete"><i class="fas fa-trash"></i></button> | |||
</div>) | |||
@@ -0,0 +1,35 @@ | |||
import { h, Component } from 'preact'; | |||
class WBBrowseDialog extends Component { | |||
constructor(...args) { | |||
super(...args); | |||
} | |||
render({ id }) { | |||
return ( | |||
<div class="modal" id={ id } tabindex="-1" role="dialog"> | |||
<div class="modal-dialog modal-lg" role="document"> | |||
<div class="modal-content"> | |||
<div class="modal-header"> | |||
<h5 class="modal-title">Browse</h5> | |||
<button type="button" class="close" data-dismiss="modal" aria-label="Close"> | |||
<span aria-hidden="true">×</span> | |||
</button> | |||
</div> | |||
<div class="modal-body m-0 p-0"> | |||
<iframe style="width: 100%;" src="/browse" /> | |||
</div> | |||
<div class="modal-footer"> | |||
<button type="button" class="btn btn-primary">Accept</button> | |||
<button type="button" class="btn btn-secondary" data-dismiss="modal">Cancel</button> | |||
</div> | |||
</div> | |||
</div> | |||
</div> | |||
); | |||
} | |||
} | |||
export default WBBrowseDialog; |
@@ -0,0 +1,77 @@ | |||
import { h, Component } from 'preact'; | |||
import WBTable from 'wb-table'; | |||
import WBNameAndUuid from 'wb-name-and-uuid'; | |||
import wbFetchObjects from 'wb-fetch-objects'; | |||
import wbFormatDate from 'wb-format-date'; | |||
class WBToolboxDialog extends Component { | |||
constructor(...args) { | |||
super(...args); | |||
this.state.rows = []; | |||
} | |||
componentDidMount() { | |||
this.fetchRows(); | |||
} | |||
componentWillReceiveProps(nextProps) { | |||
this.props = nextProps; | |||
this.fetchRows(); | |||
} | |||
fetchRows() { | |||
const { items } = this.props; | |||
const { arvHost, arvToken } = this.props.app.state; | |||
let prom = wbFetchObjects(arvHost, arvToken, | |||
items); | |||
let lookup; | |||
prom = prom.then(lkup => (lookup = lkup)); | |||
prom = prom.then(() => wbFetchObjects(arvHost, arvToken, | |||
items.map(uuid => lookup[uuid].owner_uuid))); | |||
let ownerLookup; | |||
prom = prom.then(lkup => (ownerLookup = lkup)); | |||
prom = prom.then(() => { | |||
const rows = items.map(uuid => { | |||
const it = lookup[uuid]; | |||
const ow = ownerLookup[it.owner_uuid]; | |||
return [ | |||
( <div><input type="checkbox" /></div> ), | |||
( <WBNameAndUuid uuid={ uuid } lookup={ lookup } /> ), | |||
it.kind, | |||
wbFormatDate(it.created_at), | |||
( <WBNameAndUuid uuid={ it.owner_uuid } lookup={ ownerLookup } /> ) | |||
]; | |||
}); | |||
this.setState({ rows }); | |||
}); | |||
} | |||
render({ id }, { rows }) { | |||
return ( | |||
<div class="modal" id={ id } tabindex="-1" role="dialog"> | |||
<div class="modal-dialog modal-lg" role="document"> | |||
<div class="modal-content"> | |||
<div class="modal-header"> | |||
<h5 class="modal-title">Browse Toolbox</h5> | |||
<button type="button" class="close" data-dismiss="modal" aria-label="Close"> | |||
<span aria-hidden="true">×</span> | |||
</button> | |||
</div> | |||
<div class="modal-body"> | |||
<WBTable columns={ [ '', 'Name', 'Kind', 'Created At', 'Owner' ] } | |||
rows={ rows } /> | |||
</div> | |||
<div class="modal-footer"> | |||
<button type="button" class="btn btn-primary">Accept</button> | |||
<button type="button" class="btn btn-secondary" data-dismiss="modal">Cancel</button> | |||
</div> | |||
</div> | |||
</div> | |||
</div> | |||
); | |||
} | |||
} | |||
export default WBToolboxDialog; |
@@ -8,9 +8,12 @@ function urlForObject(item, mode='primary') { | |||
return ('/browse/' + item.uuid); | |||
else if (objectType === 'container_request') | |||
return ('/process/' + item.uuid); | |||
else if (objectType === 'workflow') | |||
return ('/workflow/' + item.uuid); | |||
else if (objectType === 'collection') { | |||
else if (objectType === 'workflow') { | |||
if (mode === 'launch') | |||
return ('/workflow-launch/' + item.uuid) | |||
else | |||
return ('/workflow/' + item.uuid); | |||
} else if (objectType === 'collection') { | |||
if (mode === 'primary' || mode === 'browse') | |||
return ('/collection-browse/' + item.uuid); | |||
else | |||
@@ -19,6 +19,8 @@ class WBApp extends Component { | |||
this.state.arvToken = window.localStorage['arvToken']; | |||
if ('currentUser' in window.localStorage) | |||
this.state.currentUser = JSON.parse(window.localStorage['currentUser']); | |||
this.state.toolboxItems = ('toolboxItems' in window.localStorage) ? | |||
JSON.parse(window.localStorage['toolboxItems']) : []; | |||
} | |||
navbarItemUrl(item) { | |||
@@ -43,6 +45,12 @@ class WBApp extends Component { | |||
route('/process/' + item.uuid) | |||
} | |||
addToToolbox(uuid) { | |||
this.state.toolboxItems.push(uuid); | |||
window.localStorage['toolboxItems'] = | |||
JSON.stringify(this.state.toolboxItems); | |||
} | |||
render() { | |||
return ( | |||
<Router> | |||
@@ -38,7 +38,8 @@ class WBBrowse extends Component { | |||
<WBTabs tabs={ [ { 'name': 'Projects', 'isActive': true } ] } /> | |||
<WBProjectListing arvHost={ appState.arvHost } | |||
<WBProjectListing app={ app } | |||
arvHost={ appState.arvHost } | |||
arvToken={ appState.arvToken } | |||
ownerUuid={ ownerUuid } | |||
itemsPerPage="5" | |||
@@ -1,6 +1,7 @@ | |||
import { h, Component } from 'preact'; | |||
import WBNavbarCommon from 'wb-navbar-common'; | |||
import WBArvadosCrumbs from 'wb-arvados-crumbs'; | |||
import WBToolboxDialog from 'wb-toolbox-dialog'; | |||
import makeArvadosRequest from 'make-arvados-request'; | |||
import linkState from 'linkstate'; | |||
@@ -25,6 +26,11 @@ function createInputsTemplate(workflow) { | |||
} | |||
class WBLaunchWorkflowPage extends Component { | |||
constructor(...args) { | |||
super(...args); | |||
this.state.toolboxDialogId = uuid.v4(); | |||
} | |||
componentDidMount() { | |||
let { app, workflowUuid } = this.props; | |||
let { arvHost, arvToken } = app.state; | |||
@@ -43,12 +49,15 @@ class WBLaunchWorkflowPage extends Component { | |||
render({ app, projectUuid, workflowUuid }, | |||
{ workflow, processName, processDescription, | |||
inputsFunctionText }) { | |||
inputsFunctionText, toolboxDialogId }) { | |||
return ( | |||
<div> | |||
<WBNavbarCommon app={ app } /> | |||
<WBToolboxDialog app={ app } id={ toolboxDialogId } | |||
items={ app.state.toolboxItems } /> | |||
{ workflow ? | |||
(<form class="container-fluid"> | |||
<h1>Launch Workflow</h1> | |||
@@ -61,6 +70,7 @@ class WBLaunchWorkflowPage extends Component { | |||
<div class="form-group"> | |||
<label for="projectUuid">Project UUID</label> | |||
<input type="email" class="form-control" id="projectUuid" placeholder="Enter project uuid" /> | |||
<button class="btn btn-primary" onclick={ e => { e.preventDefault(); $('#' + toolboxDialogId).modal(); } }>Browse</button> | |||
</div> | |||
<div class="form-check"> | |||