| @@ -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"> | |||