| @@ -3,6 +3,7 @@ | |||
| "@fortawesome/fontawesome-free": "^5.12.0", | |||
| "bootstrap": "^4.4.1", | |||
| "jquery": "^3.4.1", | |||
| "js-uuid": "0.0.6", | |||
| "linkstate": "^1.1.1", | |||
| "popper.js": "^1.16.1", | |||
| "preact": "^8.2.9", | |||
| @@ -41,6 +41,7 @@ export default { | |||
| 'node_modules/@fortawesome/fontawesome-free/webfonts/fa-brands-400.ttf': 'dist/webfonts/fa-brands-400.ttf', | |||
| 'node_modules/@fortawesome/fontawesome-free/webfonts/fa-brands-400.woff': 'dist/webfonts/fa-brands-400.woff', | |||
| 'node_modules/@fortawesome/fontawesome-free/webfonts/fa-brands-400.woff2': 'dist/webfonts/fa-brands-400.woff2', | |||
| 'node_modules/js-uuid/js-uuid.js': 'dist/js/js-uuid.js', | |||
| verbose: true | |||
| }), | |||
| buble({jsx: 'h'}), | |||
| @@ -0,0 +1,7 @@ | |||
| pre.word-wrap { | |||
| white-space: pre-wrap; /* Since CSS 2.1 */ | |||
| white-space: -moz-pre-wrap; /* Mozilla, since 1999 */ | |||
| white-space: -pre-wrap; /* Opera 4-6 */ | |||
| white-space: -o-pre-wrap; /* Opera 7 */ | |||
| word-wrap: break-word; /* Internet Explorer 5.5+ */ | |||
| } | |||
| @@ -3,9 +3,11 @@ | |||
| <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> | |||
| <link rel="stylesheet" type="text/css" href="/css/bootstrap.min.css" /> | |||
| <link rel="stylesheet" type="text/css" href="/css/all.min.css" /> | |||
| <link rel="stylesheet" type="text/css" href="/css/index.css" /> | |||
| <script language="javascript" src="/js/jquery.min.js"></script> | |||
| <script language="javascript" src="/js/bootstrap.min.js"></script> | |||
| <script language="javascript" src="/js/fontawesome.min.js"></script> | |||
| <script language="javascript" src="/js/js-uuid.js"></script> | |||
| </head> | |||
| <body> | |||
| <script language="javascript" src="/js/app.min.js"></script> | |||
| @@ -69,7 +69,11 @@ class WBCommonFields extends Component { | |||
| </div> | |||
| ) ], | |||
| [ 'Modified by Client', item.modified_by_client_uuid ], | |||
| [ 'API Url', 'https://' + app.state.arvHost + '/arvados/v1' + item.href ], | |||
| [ 'API Url', ( | |||
| <a href={ 'https://' + app.state.arvHost + '/arvados/v1' + item.href }> | |||
| { 'https://' + app.state.arvHost + '/arvados/v1' + item.href } | |||
| </a> | |||
| ) ], | |||
| [ 'ETag', item.etag ] | |||
| ]; | |||
| this.setState({ 'rows': rows }); | |||
| @@ -78,13 +82,12 @@ class WBCommonFields extends Component { | |||
| render({}, { rows }) { | |||
| return ( | |||
| <div> | |||
| <h1>Common Fields</h1> | |||
| { rows ? ( | |||
| <WBTable columns={ [ "Name", "Value" ] } | |||
| rows={ rows } /> | |||
| ) : <div>Loading...</div> } | |||
| </div> | |||
| rows ? ( | |||
| <WBTable columns={ [ "Name", "Value" ] } | |||
| rows={ rows } /> | |||
| ) : ( | |||
| <div>Loading...</div> | |||
| ) | |||
| ); | |||
| } | |||
| } | |||
| @@ -0,0 +1,124 @@ | |||
| import { h, Component } from 'preact'; | |||
| import WBTable from 'wb-table'; | |||
| import makeArvadosRequest from 'make-arvados-request'; | |||
| import arvadosTypeName from 'arvados-type-name'; | |||
| import arvadosObjectName from 'arvados-object-name'; | |||
| import urlForObject from 'url-for-object'; | |||
| import wbFormatDate from 'wb-format-date'; | |||
| import WBNameAndUuid from 'wb-name-and-uuid'; | |||
| import WBAccordion from 'wb-accordion'; | |||
| class WBContainerRequestFields extends Component { | |||
| componentDidMount() { | |||
| this.prepareRows(); | |||
| } | |||
| componentWillReceiveProps(nextProps) { | |||
| this.props = nextProps; | |||
| this.prepareRows(); | |||
| } | |||
| prepareRows() { | |||
| let { uuid, app } = this.props; | |||
| let { arvHost, arvToken } = app.state; | |||
| let item; | |||
| let prom = makeArvadosRequest(arvHost, arvToken, | |||
| '/arvados/v1/container_requests/' + uuid); | |||
| prom = prom.then(xhr => (item = xhr.response)); | |||
| prom = prom.then(() => { | |||
| let rows = [ | |||
| [ 'Name', item.name ], | |||
| [ 'Description', item.description || (<i>{ String(item.description) }</i>) ], | |||
| [ 'Properties', ( | |||
| <WBAccordion names={ ['Properties'] } cardHeaderClass="card-header-sm"> | |||
| <pre class="word-wrap">{ JSON.stringify(item.properties, null, 2) }</pre> | |||
| </WBAccordion> | |||
| ) ], | |||
| [ 'State', item.state ], | |||
| [ 'Requesting Container', ( | |||
| <WBNameAndUuid app={ app } uuid={ item.requesting_container_uuid } /> | |||
| ) ], | |||
| [ 'Container', ( | |||
| <WBNameAndUuid app={ app } uuid={ item.container_uuid } /> | |||
| ) ], | |||
| [ 'Container Count Max', item.container_count_max ], | |||
| [ 'Mounts', ( | |||
| <WBAccordion names={ Object.keys(item.mounts) } | |||
| cardHeaderClass="card-header-sm"> | |||
| { Object.keys(item.mounts).map(k => ( | |||
| <pre class="word-wrap">{ JSON.stringify(item.mounts[k], null, 2) }</pre> | |||
| )) } | |||
| </WBAccordion> | |||
| ) ], | |||
| [ 'Runtime Constraints', ( | |||
| <WBAccordion names={ ['Runtime Constraints'] } | |||
| cardHeaderClass="card-header-sm"> | |||
| <pre class="word-wrap">{ JSON.stringify(item.runtime_constraints, null, 2) }</pre> | |||
| </WBAccordion> | |||
| ) ], | |||
| [ 'Scheduling Parameters', ( | |||
| <WBAccordion names={ ['Scheduling Parameters'] } | |||
| cardHeaderClass="card-header-sm"> | |||
| <pre class="word-wrap">{ JSON.stringify(item.scheduling_parameters, null, 2) }</pre> | |||
| </WBAccordion> | |||
| ) ], | |||
| [ 'Container Image', ( | |||
| <WBNameAndUuid app={ app } uuid={ item.container_image } /> | |||
| ) ], | |||
| [ 'Environment', ( | |||
| <WBAccordion names={ ['Environment'] } | |||
| cardHeaderClass="card-header-sm"> | |||
| <pre class="word-wrap">{ JSON.stringify(item.environment, null, 2) }</pre> | |||
| </WBAccordion> | |||
| ) ], | |||
| [ 'Working Directory', item.cwd ], | |||
| [ 'Command', ( | |||
| <pre class="word-wrap">{ JSON.stringify(item.command) }</pre> | |||
| ) ], | |||
| [ 'Output Path', item.output_path ], | |||
| [ 'Output Name', item.output_name ], | |||
| [ 'Output TTL', item.output_ttl ], | |||
| [ 'Priority', item.priority ], | |||
| [ 'Expires At', wbFormatDate(item.expires_at) ], | |||
| [ 'Use Existing', String(item.use_existing) ], | |||
| [ 'Log', ( | |||
| <WBNameAndUuid app={ app } uuid={ item.log_uuid } /> | |||
| ) ], | |||
| [ 'Output', ( | |||
| <WBNameAndUuid app={ app } uuid={ item.output_uuid } /> | |||
| ) ], | |||
| [ 'Filters', ( | |||
| item.filters ? (<pre class="word-wrap">{ item.filters }</pre>) : (<i>{ String(item.filters) }</i>) | |||
| ) ], | |||
| [ 'Runtime Token', item.runtime_token || (<i>{ String(item.runtime_token) }</i>) ], | |||
| [ 'Runtime User', ( | |||
| <WBNameAndUuid app={ app } uuid={ item.runtime_user } /> | |||
| ) ], | |||
| [ 'Runtime Auth Scopes', ( | |||
| item.runtime_auth_scopes ? ( | |||
| <pre class="word-wrap">{ JSON.stringify(item.runtime_auth_scopes, null, 2) }</pre> | |||
| ) : ( | |||
| <i>{ String(item.runtime_auth_scopes) }</i> | |||
| ) | |||
| ) ] | |||
| ]; | |||
| this.setState({ 'rows': rows }); | |||
| }); | |||
| } | |||
| render({}, { rows }) { | |||
| return ( | |||
| rows ? ( | |||
| <WBTable columns={ [ "Name", "Value" ] } | |||
| headerClasses={ [ "col-sm-2", "col-sm-4" ] } | |||
| rows={ rows } /> | |||
| ) : ( | |||
| <div>Loading...</div> | |||
| ) | |||
| ); | |||
| } | |||
| } | |||
| export default WBContainerRequestFields; | |||
| @@ -0,0 +1,78 @@ | |||
| import { h, Component } from 'preact'; | |||
| import makeArvadosRequest from 'make-arvados-request'; | |||
| import urlForObject from 'url-for-object'; | |||
| import arvadosObjectName from 'arvados-object-name'; | |||
| import arvadosTypeName from 'arvados-type-name'; | |||
| class WBNameAndUuid extends Component { | |||
| componentDidMount() { | |||
| let { uuid, app } = this.props; | |||
| let { arvHost, arvToken } = app.state; | |||
| if (!uuid) | |||
| return; | |||
| let prom = new Promise(accept => accept()); | |||
| if (/[0-9a-f]{32}\+[0-9]+/g.exec(uuid)) { | |||
| let filters = [ | |||
| ['portable_data_hash', '=', uuid] | |||
| ]; | |||
| prom = prom.then(() => makeArvadosRequest(arvHost, arvToken, | |||
| '/arvados/v1/collections?filters=' + | |||
| encodeURIComponent(JSON.stringify(filters)))); | |||
| prom = prom.then(xhr => { | |||
| if (xhr.response.items.length === 0) { | |||
| this.setState({ | |||
| 'item': { | |||
| 'uuid': uuid, | |||
| 'name': 'Collection with portable data hash ' + uuid | |||
| } | |||
| }); | |||
| return; | |||
| } | |||
| let item = xhr.response.items[0]; | |||
| if (xhr.response.items.length > 1) | |||
| item.name += ' +' + (xhr.response.items.length - 1) + ' others'; | |||
| this.setState({ item }); | |||
| }); | |||
| } else if (/[a-z0-9]{5}-[a-z0-9]{5}-[a-z0-9]{15}/.exec(uuid)) { | |||
| let typeName = arvadosTypeName(uuid); | |||
| let prom = makeArvadosRequest(arvHost, arvToken, | |||
| '/arvados/v1/' + typeName + 's/' + uuid ); | |||
| prom = prom.then(xhr => this.setState({ | |||
| 'item': xhr.response | |||
| })); | |||
| } else { | |||
| this.setState({ | |||
| 'item': { | |||
| 'uuid': uuid | |||
| } | |||
| }); | |||
| } | |||
| } | |||
| render({ uuid }, { item }) { | |||
| if (!uuid) | |||
| return ( | |||
| <div><i>{ String(uuid) }</i></div> | |||
| ); | |||
| return ( | |||
| <div> | |||
| <div> | |||
| { item ? ( | |||
| <a href={ urlForObject(item) }>{ arvadosObjectName(item) }</a> | |||
| ) : 'Loading...' } | |||
| </div> | |||
| <div> | |||
| { uuid } | |||
| </div> | |||
| </div> | |||
| ); | |||
| } | |||
| } | |||
| export default WBNameAndUuid; | |||
| @@ -4,6 +4,8 @@ function arvadosObjectName(item) { | |||
| let typeName = arvadosTypeName(item['uuid']); | |||
| if (typeName === 'user') | |||
| return (item.first_name + ' ' + item.last_name); | |||
| else if (typeName === 'container') | |||
| return ('Container running image ' + item.container_image); | |||
| else | |||
| return item.name; | |||
| } | |||
| @@ -9,6 +9,9 @@ const typeIdToName = { | |||
| }; | |||
| function arvadosTypeName(id) { | |||
| if (!id) | |||
| return; | |||
| if (id.length === 5) | |||
| return typeIdToName[id]; | |||
| else | |||
| @@ -7,6 +7,7 @@ import arvadosTypeName from 'arvados-type-name'; | |||
| import urlForObject from 'url-for-object'; | |||
| import detectHashes from 'detect-hashes'; | |||
| import WBCommonFields from 'wb-common-fields'; | |||
| import WBContainerRequestFields from 'wb-container-request-fields'; | |||
| class WBProcessView extends Component { | |||
| constructor(...args) { | |||
| @@ -88,8 +89,12 @@ class WBProcessView extends Component { | |||
| This is the process view for { uuid } | |||
| </div> | |||
| <h2>Common Fields</h2> | |||
| <WBCommonFields app={ app } uuid={ uuid } /> | |||
| <h2>Container Request Fields</h2> | |||
| <WBContainerRequestFields app={ app } uuid={ uuid } /> | |||
| <div> | |||
| <a href="/browse">Click here</a> | |||
| </div> | |||
| @@ -0,0 +1,40 @@ | |||
| import { h, Component } from 'preact'; | |||
| class WBAccordion extends Component { | |||
| constructor(...args) { | |||
| super(...args); | |||
| this.state.domId = 'accordion-' + uuid.v4(); | |||
| this.state.headerDomIds = this.props.names.map(() => ('accordion-' + uuid.v4())); | |||
| this.state.collapseDomIds = this.props.names.map(() => ('accordion-' + uuid.v4())); | |||
| } | |||
| render({ children, names, cardHeaderClass }, { domId, headerDomIds, collapseDomIds }) { | |||
| return ( | |||
| <div class="accordion" id={ domId }> | |||
| { children.map((_, i) => ( | |||
| <div class="card"> | |||
| <div class={ cardHeaderClass} id={ headerDomIds[i] }> | |||
| <h2 class="mb-0"> | |||
| <button class="btn btn-link" type="button" data-toggle="collapse" data-target={ '#' + collapseDomIds[i] } aria-expanded="false" aria-controls={ collapseDomIds[i] }> | |||
| { names[i] } | |||
| </button> | |||
| </h2> | |||
| </div> | |||
| <div id={ collapseDomIds[i] } class="collapse" aria-labelledby={ headerDomIds[i] } data-parent={ '#' + domId }> | |||
| <div class="card-body"> | |||
| { children[i] } | |||
| </div> | |||
| </div> | |||
| </div> | |||
| )) } | |||
| </div> | |||
| ); | |||
| } | |||
| }; | |||
| WBAccordion.defaultProps = { | |||
| 'cardHeaderClass': 'card-header' | |||
| }; | |||
| export default WBAccordion; | |||
| @@ -1,12 +1,12 @@ | |||
| import { h, Component } from 'preact'; | |||
| class WBTable extends Component { | |||
| render({ columns, rows }) { | |||
| render({ columns, rows, headerClasses }) { | |||
| return ( | |||
| <table class="table table-striped table-hover"> | |||
| <thead class="thead-light"> | |||
| <tr> | |||
| { columns.map(c => <th>{ c }</th>) } | |||
| { columns.map((c, i) => <th class={ headerClasses[i] }>{ c }</th>) } | |||
| </tr> | |||
| </thead> | |||
| <tbody> | |||
| @@ -23,4 +23,8 @@ class WBTable extends Component { | |||
| } | |||
| } | |||
| WBTable.defaultProps = { | |||
| 'headerClasses': [] | |||
| }; | |||
| export default WBTable; | |||