| @@ -22,7 +22,7 @@ class WBCollectionListing extends Component { | |||||
| prepareRows(items, ownerLookup) { | prepareRows(items, ownerLookup) { | ||||
| let { app, renderRenameLink, renderDeleteButton, | let { app, renderRenameLink, renderDeleteButton, | ||||
| renderSelectionCell } = this.props; | |||||
| renderSelectionCell, renderSharingButton } = this.props; | |||||
| return items.map(item => [ | return items.map(item => [ | ||||
| renderSelectionCell(item), | renderSelectionCell(item), | ||||
| @@ -59,6 +59,8 @@ class WBCollectionListing extends Component { | |||||
| </a> | </a> | ||||
| { renderDeleteButton(item, () => this.fetchItems()) } | { renderDeleteButton(item, () => this.fetchItems()) } | ||||
| { renderSharingButton(item) } | |||||
| </div>) | </div>) | ||||
| ]); | ]); | ||||
| } | } | ||||
| @@ -29,7 +29,7 @@ class WBProcessListing extends Component { | |||||
| prepareRows(requests, containerLookup, ownerLookup, outputLookup) { | prepareRows(requests, containerLookup, ownerLookup, outputLookup) { | ||||
| const { app, renderRenameLink, renderDeleteButton, | const { app, renderRenameLink, renderDeleteButton, | ||||
| renderSelectionCell } = this.props; | |||||
| renderSelectionCell, renderSharingButton } = this.props; | |||||
| return requests.map(item => { | return requests.map(item => { | ||||
| return ( [ | return ( [ | ||||
| renderSelectionCell(item), | renderSelectionCell(item), | ||||
| @@ -47,6 +47,7 @@ class WBProcessListing extends Component { | |||||
| ( <WBNameAndUuid app={ app } uuid={ item['output_uuid'] } /> ), | ( <WBNameAndUuid app={ app } uuid={ item['output_uuid'] } /> ), | ||||
| (<div> | (<div> | ||||
| { renderDeleteButton(item, () => this.fetchItems()) } | { renderDeleteButton(item, () => this.fetchItems()) } | ||||
| { renderSharingButton(item) } | |||||
| </div>) | </div>) | ||||
| ] ); | ] ); | ||||
| }); | }); | ||||
| @@ -22,7 +22,7 @@ class WBWorkflowListing extends Component { | |||||
| prepareRows(items, ownerLookup) { | prepareRows(items, ownerLookup) { | ||||
| const { renderRenameLink, renderDeleteButton, | const { renderRenameLink, renderDeleteButton, | ||||
| renderSelectionCell } = this.props; | |||||
| renderSelectionCell, renderSharingButton } = this.props; | |||||
| return items.map(item => [ | return items.map(item => [ | ||||
| renderSelectionCell(item), | renderSelectionCell(item), | ||||
| ( | ( | ||||
| @@ -43,6 +43,7 @@ class WBWorkflowListing extends Component { | |||||
| href={ urlForObject(item, 'launch') }><i class="fas fa-running"></i></a> | 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> | ||||
| { renderDeleteButton(item, () => this.fetchItems()) } | { renderDeleteButton(item, () => this.fetchItems()) } | ||||
| { renderSharingButton(item) } | |||||
| </div>) | </div>) | ||||
| ]); | ]); | ||||
| } | } | ||||
| @@ -194,7 +194,8 @@ class WBBrowse extends Component { | |||||
| getPageUrl={ i => this.getUrl({ 'collectionPage': i }) } | getPageUrl={ i => this.getUrl({ 'collectionPage': i }) } | ||||
| renderRenameLink={ (it, cb) => this.renderRenameLink(it, cb) } | renderRenameLink={ (it, cb) => this.renderRenameLink(it, cb) } | ||||
| renderDeleteButton={ (it, cb) => this.renderDeleteButton(it, cb) } | renderDeleteButton={ (it, cb) => this.renderDeleteButton(it, cb) } | ||||
| renderSelectionCell={ it => this.renderSelectionCell(it) } /> | |||||
| renderSelectionCell={ it => this.renderSelectionCell(it) } | |||||
| renderSharingButton={ it => this.renderSharingButton(it) } /> | |||||
| ) : (objTypeTab === 'process') ? ( | ) : (objTypeTab === 'process') ? ( | ||||
| <WBProcessListing app={ app } | <WBProcessListing app={ app } | ||||
| @@ -205,7 +206,8 @@ class WBBrowse extends Component { | |||||
| onPageChanged={ i => this.route({ 'processPage': i }) } | onPageChanged={ i => this.route({ 'processPage': i }) } | ||||
| renderRenameLink={ (it, cb) => this.renderRenameLink(it, cb) } | renderRenameLink={ (it, cb) => this.renderRenameLink(it, cb) } | ||||
| renderDeleteButton={ (it, cb) => this.renderDeleteButton(it, cb) } | renderDeleteButton={ (it, cb) => this.renderDeleteButton(it, cb) } | ||||
| renderSelectionCell={ it => this.renderSelectionCell(it) } /> | |||||
| renderSelectionCell={ it => this.renderSelectionCell(it) } | |||||
| renderSharingButton={ it => this.renderSharingButton(it) } /> | |||||
| ) : (objTypeTab === 'workflow') ? ( | ) : (objTypeTab === 'workflow') ? ( | ||||
| <WBWorkflowListing app={ app } | <WBWorkflowListing app={ app } | ||||
| @@ -215,7 +217,8 @@ class WBBrowse extends Component { | |||||
| getPageUrl={ i => this.getUrl({ 'workflowPage': i }) } | getPageUrl={ i => this.getUrl({ 'workflowPage': i }) } | ||||
| renderRenameLink={ (it, cb) => this.renderRenameLink(it, cb) } | renderRenameLink={ (it, cb) => this.renderRenameLink(it, cb) } | ||||
| renderDeleteButton={ (it, cb) => this.renderDeleteButton(it, cb) } | renderDeleteButton={ (it, cb) => this.renderDeleteButton(it, cb) } | ||||
| renderSelectionCell={ it => this.renderSelectionCell(it) } /> | |||||
| renderSelectionCell={ it => this.renderSelectionCell(it) } | |||||
| renderSharingButton={ it => this.renderSharingButton(it) } /> | |||||
| ) : null | ) : null | ||||
| } | } | ||||
| @@ -22,37 +22,116 @@ class WBSharingPage extends Component { | |||||
| const { app, uuid } = this.props; | const { app, uuid } = this.props; | ||||
| const { arvHost, arvToken } = app.state; | const { arvHost, arvToken } = app.state; | ||||
| let prom = makeArvadosRequest(arvHost, arvToken, | let prom = makeArvadosRequest(arvHost, arvToken, | ||||
| '/arvados/v1/permissions/' + encodeURIComponent(uuid)); | |||||
| '/arvados/v1/permissions/' + encodeURIComponent(uuid) + | |||||
| '?limit=100000'); | |||||
| prom = prom.then(xhr => this.setState({ | prom = prom.then(xhr => this.setState({ | ||||
| 'entries': xhr.response.items, | |||||
| 'rows': this.prepareRows(xhr.response.items) | 'rows': this.prepareRows(xhr.response.items) | ||||
| })); | })); | ||||
| } | } | ||||
| deletePermission(uuid) { | |||||
| throw Error('Not implemented'); | |||||
| deleteEntry(it) { | |||||
| it._delete = true; | |||||
| this.setState({ rows: this.prepareRows(this.state.entries) }); | |||||
| } | } | ||||
| prepareRows(items) { | prepareRows(items) { | ||||
| const { app } = this.props; | const { app } = this.props; | ||||
| return items.map(it => [ | |||||
| return items.filter(it => (!it._delete)).map(it => [ | |||||
| ( <WBNameAndUuid app={ app } uuid={ it.tail_uuid } /> ), | ( <WBNameAndUuid app={ app } uuid={ it.tail_uuid } /> ), | ||||
| ( <WBSelect value={ it.name } options={ ['can_read', 'can_write', 'can_manage'] } /> ), | |||||
| ( <WBSelect value={ it.name } | |||||
| options={ ['can_read', 'can_write', 'can_manage'] } | |||||
| onChange={ e => this.modifyEntry(it, e.target.value) } /> ), | |||||
| ( <button class="btn btn-outline-danger m-1" title="Delete" | ( <button class="btn btn-outline-danger m-1" title="Delete" | ||||
| onclick={ () => this.deletePermission(it.uuid) }> | |||||
| onclick={ () => this.deleteEntry(it) }> | |||||
| <i class="fas fa-trash"></i> | <i class="fas fa-trash"></i> | ||||
| </button> ) | </button> ) | ||||
| ]); | ]); | ||||
| } | } | ||||
| addEntry(it) { | |||||
| throw Error('Not implemented'); | |||||
| modifyEntry(it, newPermissionName) { | |||||
| it.name = newPermissionName; | |||||
| it._dirty = true; | |||||
| // this.setState({ rows: this.prepareRows(this.state.entries) }); | |||||
| } | |||||
| addEntry(it, permissionName='can_read') { | |||||
| // throw Error('Not implemented'); | |||||
| const { uuid } = this.props; | |||||
| let { entries } = this.state; | |||||
| if (entries.filter(e => (e.tail_uuid === it.uuid)).length > 0) | |||||
| return; // already in the list | |||||
| const e = { | |||||
| //_dirty: true, | |||||
| link_class: 'permission', | |||||
| head_uuid: uuid, | |||||
| tail_uuid: it.uuid, | |||||
| name: permissionName | |||||
| }; | |||||
| entries = entries.concat([e]); | |||||
| this.setState({ | |||||
| entries, | |||||
| rows: this.prepareRows(entries) | |||||
| }); | |||||
| } | |||||
| disableControls() { | |||||
| $('input, select, button').attr('disabled', 'disabled'); | |||||
| $('a').each(function() { $(this).data('old_href', $(this).attr('href')); }); | |||||
| $('a').attr('href', null); | |||||
| } | |||||
| enableControls() { | |||||
| $('input, select, button').attr('disabled', null); | |||||
| $('a').each(function() { $(this).attr('href', $(this).data('old_href')); }); | |||||
| } | } | ||||
| save() { | save() { | ||||
| throw Error('Not implemented'); | |||||
| const { entries } = this.state; | |||||
| const { arvHost, arvToken } = this.props.app.state; | |||||
| let prom = new Promise(accept => accept()); | |||||
| this.disableControls(); | |||||
| this.setState({ working: true }); | |||||
| for (let i = 0; i < entries.length; i++) { | |||||
| const e = entries[i]; | |||||
| //if (!e._dirty && !e._delete) | |||||
| //continue; | |||||
| if (!e.uuid) { | |||||
| prom = prom.then(() => makeArvadosRequest(arvHost, arvToken, | |||||
| '/arvados/v1/links', | |||||
| { 'method': 'POST', | |||||
| 'data': JSON.stringify({ | |||||
| 'link_class': 'permission', | |||||
| 'head_uuid': e.head_uuid, | |||||
| 'tail_uuid': e.tail_uuid, | |||||
| 'name': e.name | |||||
| }) })); | |||||
| } else if (e._delete) { | |||||
| prom = prom.then(() => makeArvadosRequest(arvHost, arvToken, | |||||
| '/arvados/v1/links/' + e.uuid, | |||||
| { 'method': 'DELETE' })); | |||||
| } else if (e._dirty) { | |||||
| prom = prom.then(() => makeArvadosRequest(arvHost, arvToken, | |||||
| '/arvados/v1/links/' + e.uuid, | |||||
| { 'method': 'PUT', | |||||
| 'data': JSON.stringify({ | |||||
| 'name': e.name | |||||
| }) })); | |||||
| } | |||||
| prom = prom.catch(() => {}); | |||||
| } | |||||
| prom = prom.then(() => { | |||||
| this.enableControls(); | |||||
| this.fetchData(); | |||||
| this.setState({ working: false }); | |||||
| }); | |||||
| } | } | ||||
| render({ app, uuid }, { rows }) { | |||||
| render({ app, uuid }, { rows, working }) { | |||||
| return ( | return ( | ||||
| <div> | <div> | ||||
| <WBNavbarCommon app={ app } /> | <WBNavbarCommon app={ app } /> | ||||
| @@ -70,6 +149,12 @@ class WBSharingPage extends Component { | |||||
| <WBPickObjectDialog app={ app } ref={ this.dialogRef } /> | <WBPickObjectDialog app={ app } ref={ this.dialogRef } /> | ||||
| { working ? (<div class="progress my-2"> | |||||
| <div class={ 'progress-bar progress-bar-striped progress-bar-animated' } | |||||
| role="progressbar" aria-valuenow="100" aria-valuemin="0" | |||||
| aria-valuemax="100" style="width: 100%"></div> | |||||
| </div>) : null } | |||||
| <button class="btn btn-outline-secondary mr-2" | <button class="btn btn-outline-secondary mr-2" | ||||
| onclick={ () => this.dialogRef.current.show('Select User', 'user', it => this.addEntry(it)) }>Add User...</button> | onclick={ () => this.dialogRef.current.show('Select User', 'user', it => this.addEntry(it)) }>Add User...</button> | ||||
| <button class="btn btn-outline-secondary mr-2" | <button class="btn btn-outline-secondary mr-2" | ||||