| @@ -4,4 +4,4 @@ node_modules | |||||
| package-lock.json | package-lock.json | ||||
| /frontend/dist/ | /frontend/dist/ | ||||
| /backend/server.pem | /backend/server.pem | ||||
| /testdata/ | |||||
| @@ -16,7 +16,7 @@ export default { | |||||
| }, | }, | ||||
| plugins: [ | plugins: [ | ||||
| includePaths({ | 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({ | copy({ | ||||
| 'src/html/index.html': 'dist/index.html', | 'src/html/index.html': 'dist/index.html', | ||||
| @@ -21,6 +21,8 @@ class WBCollectionListing extends Component { | |||||
| } | } | ||||
| prepareRows(items, ownerLookup) { | prepareRows(items, ownerLookup) { | ||||
| let { app } = this.props; | |||||
| return items.map(item => [ | return items.map(item => [ | ||||
| (<div> | (<div> | ||||
| <div> | <div> | ||||
| @@ -42,10 +44,16 @@ class WBCollectionListing extends Component { | |||||
| item['file_count'], | item['file_count'], | ||||
| filesize(item['file_size_total']), | filesize(item['file_size_total']), | ||||
| (<div> | (<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" | <a class="btn btn-outline-primary m-1" title="Properties" | ||||
| href={ urlForObject(item, 'properties') }> | href={ urlForObject(item, 'properties') }> | ||||
| <i class="fas fa-list-ul"></i> | <i class="fas fa-list-ul"></i> | ||||
| </a> | </a> | ||||
| <button class="btn btn-outline-danger m-1" title="Delete"> | <button class="btn btn-outline-danger m-1" title="Delete"> | ||||
| <i class="fas fa-trash"></i> | <i class="fas fa-trash"></i> | ||||
| </button> | </button> | ||||
| @@ -17,6 +17,8 @@ class WBProjectListing extends Component { | |||||
| } | } | ||||
| prepareRows(items) { | prepareRows(items) { | ||||
| let { app } = this.props; | |||||
| return items.map(item => [ | return items.map(item => [ | ||||
| (<div> | (<div> | ||||
| <div> | <div> | ||||
| @@ -26,7 +28,13 @@ class WBProjectListing extends Component { | |||||
| <div>{ item['uuid'] }</div> | <div>{ item['uuid'] }</div> | ||||
| </div>), | </div>), | ||||
| item['description'], | 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 }) { | render({ arvHost, arvToken, ownerUuid, activePage, onPageChanged }, { rows, numPages }) { | ||||
| return ( | return ( | ||||
| <div> | <div> | ||||
| <WBTable columns={ [ 'Name', 'Description', 'Owner' ] } | |||||
| <WBTable columns={ [ 'Name', 'Description', 'Owner', 'Actions' ] } | |||||
| rows={ rows } /> | rows={ rows } /> | ||||
| <WBPagination numPages={ numPages } | <WBPagination numPages={ numPages } | ||||
| activePage={ activePage } | activePage={ activePage } | ||||
| onPageChanged={ i => onPageChanged(i) } /> | onPageChanged={ i => onPageChanged(i) } /> | ||||
| @@ -5,6 +5,7 @@ import WBPagination from 'wb-pagination'; | |||||
| import WBNameAndUuid from 'wb-name-and-uuid'; | import WBNameAndUuid from 'wb-name-and-uuid'; | ||||
| import wbFetchObjects from 'wb-fetch-objects'; | import wbFetchObjects from 'wb-fetch-objects'; | ||||
| import wbFormatDate from 'wb-format-date'; | import wbFormatDate from 'wb-format-date'; | ||||
| import urlForObject from 'url-for-object'; | |||||
| class WBWorkflowListing extends Component { | class WBWorkflowListing extends Component { | ||||
| @@ -25,7 +26,8 @@ class WBWorkflowListing extends Component { | |||||
| ( <WBNameAndUuid uuid={ item.owner_uuid } lookup={ ownerLookup } /> ), | ( <WBNameAndUuid uuid={ item.owner_uuid } lookup={ ownerLookup } /> ), | ||||
| wbFormatDate(item.created_at), | wbFormatDate(item.created_at), | ||||
| (<div> | (<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-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> | <button class="btn btn-outline-danger mx-1 my-1" title="Delete"><i class="fas fa-trash"></i></button> | ||||
| </div>) | </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); | return ('/browse/' + item.uuid); | ||||
| else if (objectType === 'container_request') | else if (objectType === 'container_request') | ||||
| return ('/process/' + item.uuid); | 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') | if (mode === 'primary' || mode === 'browse') | ||||
| return ('/collection-browse/' + item.uuid); | return ('/collection-browse/' + item.uuid); | ||||
| else | else | ||||
| @@ -19,6 +19,8 @@ class WBApp extends Component { | |||||
| this.state.arvToken = window.localStorage['arvToken']; | this.state.arvToken = window.localStorage['arvToken']; | ||||
| if ('currentUser' in window.localStorage) | if ('currentUser' in window.localStorage) | ||||
| this.state.currentUser = JSON.parse(window.localStorage['currentUser']); | this.state.currentUser = JSON.parse(window.localStorage['currentUser']); | ||||
| this.state.toolboxItems = ('toolboxItems' in window.localStorage) ? | |||||
| JSON.parse(window.localStorage['toolboxItems']) : []; | |||||
| } | } | ||||
| navbarItemUrl(item) { | navbarItemUrl(item) { | ||||
| @@ -43,6 +45,12 @@ class WBApp extends Component { | |||||
| route('/process/' + item.uuid) | route('/process/' + item.uuid) | ||||
| } | } | ||||
| addToToolbox(uuid) { | |||||
| this.state.toolboxItems.push(uuid); | |||||
| window.localStorage['toolboxItems'] = | |||||
| JSON.stringify(this.state.toolboxItems); | |||||
| } | |||||
| render() { | render() { | ||||
| return ( | return ( | ||||
| <Router> | <Router> | ||||
| @@ -38,7 +38,8 @@ class WBBrowse extends Component { | |||||
| <WBTabs tabs={ [ { 'name': 'Projects', 'isActive': true } ] } /> | <WBTabs tabs={ [ { 'name': 'Projects', 'isActive': true } ] } /> | ||||
| <WBProjectListing arvHost={ appState.arvHost } | |||||
| <WBProjectListing app={ app } | |||||
| arvHost={ appState.arvHost } | |||||
| arvToken={ appState.arvToken } | arvToken={ appState.arvToken } | ||||
| ownerUuid={ ownerUuid } | ownerUuid={ ownerUuid } | ||||
| itemsPerPage="5" | itemsPerPage="5" | ||||
| @@ -1,6 +1,7 @@ | |||||
| import { h, Component } from 'preact'; | import { h, Component } from 'preact'; | ||||
| import WBNavbarCommon from 'wb-navbar-common'; | import WBNavbarCommon from 'wb-navbar-common'; | ||||
| import WBArvadosCrumbs from 'wb-arvados-crumbs'; | import WBArvadosCrumbs from 'wb-arvados-crumbs'; | ||||
| import WBToolboxDialog from 'wb-toolbox-dialog'; | |||||
| import makeArvadosRequest from 'make-arvados-request'; | import makeArvadosRequest from 'make-arvados-request'; | ||||
| import linkState from 'linkstate'; | import linkState from 'linkstate'; | ||||
| @@ -25,6 +26,11 @@ function createInputsTemplate(workflow) { | |||||
| } | } | ||||
| class WBLaunchWorkflowPage extends Component { | class WBLaunchWorkflowPage extends Component { | ||||
| constructor(...args) { | |||||
| super(...args); | |||||
| this.state.toolboxDialogId = uuid.v4(); | |||||
| } | |||||
| componentDidMount() { | componentDidMount() { | ||||
| let { app, workflowUuid } = this.props; | let { app, workflowUuid } = this.props; | ||||
| let { arvHost, arvToken } = app.state; | let { arvHost, arvToken } = app.state; | ||||
| @@ -43,12 +49,15 @@ class WBLaunchWorkflowPage extends Component { | |||||
| render({ app, projectUuid, workflowUuid }, | render({ app, projectUuid, workflowUuid }, | ||||
| { workflow, processName, processDescription, | { workflow, processName, processDescription, | ||||
| inputsFunctionText }) { | |||||
| inputsFunctionText, toolboxDialogId }) { | |||||
| return ( | return ( | ||||
| <div> | <div> | ||||
| <WBNavbarCommon app={ app } /> | <WBNavbarCommon app={ app } /> | ||||
| <WBToolboxDialog app={ app } id={ toolboxDialogId } | |||||
| items={ app.state.toolboxItems } /> | |||||
| { workflow ? | { workflow ? | ||||
| (<form class="container-fluid"> | (<form class="container-fluid"> | ||||
| <h1>Launch Workflow</h1> | <h1>Launch Workflow</h1> | ||||
| @@ -61,6 +70,7 @@ class WBLaunchWorkflowPage extends Component { | |||||
| <div class="form-group"> | <div class="form-group"> | ||||
| <label for="projectUuid">Project UUID</label> | <label for="projectUuid">Project UUID</label> | ||||
| <input type="email" class="form-control" id="projectUuid" placeholder="Enter project uuid" /> | <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> | ||||
| <div class="form-check"> | <div class="form-check"> | ||||