| @@ -3,6 +3,7 @@ | |||||
| "@fortawesome/fontawesome-free": "^5.12.0", | "@fortawesome/fontawesome-free": "^5.12.0", | ||||
| "bootstrap": "^4.4.1", | "bootstrap": "^4.4.1", | ||||
| "jquery": "^3.4.1", | "jquery": "^3.4.1", | ||||
| "js-uuid": "0.0.6", | |||||
| "linkstate": "^1.1.1", | "linkstate": "^1.1.1", | ||||
| "popper.js": "^1.16.1", | "popper.js": "^1.16.1", | ||||
| "preact": "^8.2.9", | "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.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.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/@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 | verbose: true | ||||
| }), | }), | ||||
| buble({jsx: 'h'}), | 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" /> | <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/bootstrap.min.css" /> | ||||
| <link rel="stylesheet" type="text/css" href="/css/all.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/jquery.min.js"></script> | ||||
| <script language="javascript" src="/js/bootstrap.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/fontawesome.min.js"></script> | ||||
| <script language="javascript" src="/js/js-uuid.js"></script> | |||||
| </head> | </head> | ||||
| <body> | <body> | ||||
| <script language="javascript" src="/js/app.min.js"></script> | <script language="javascript" src="/js/app.min.js"></script> | ||||
| @@ -69,7 +69,11 @@ class WBCommonFields extends Component { | |||||
| </div> | </div> | ||||
| ) ], | ) ], | ||||
| [ 'Modified by Client', item.modified_by_client_uuid ], | [ '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 ] | [ 'ETag', item.etag ] | ||||
| ]; | ]; | ||||
| this.setState({ 'rows': rows }); | this.setState({ 'rows': rows }); | ||||
| @@ -78,13 +82,12 @@ class WBCommonFields extends Component { | |||||
| render({}, { rows }) { | render({}, { rows }) { | ||||
| return ( | 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']); | let typeName = arvadosTypeName(item['uuid']); | ||||
| if (typeName === 'user') | if (typeName === 'user') | ||||
| return (item.first_name + ' ' + item.last_name); | return (item.first_name + ' ' + item.last_name); | ||||
| else if (typeName === 'container') | |||||
| return ('Container running image ' + item.container_image); | |||||
| else | else | ||||
| return item.name; | return item.name; | ||||
| } | } | ||||
| @@ -9,6 +9,9 @@ const typeIdToName = { | |||||
| }; | }; | ||||
| function arvadosTypeName(id) { | function arvadosTypeName(id) { | ||||
| if (!id) | |||||
| return; | |||||
| if (id.length === 5) | if (id.length === 5) | ||||
| return typeIdToName[id]; | return typeIdToName[id]; | ||||
| else | else | ||||
| @@ -7,6 +7,7 @@ import arvadosTypeName from 'arvados-type-name'; | |||||
| import urlForObject from 'url-for-object'; | import urlForObject from 'url-for-object'; | ||||
| import detectHashes from 'detect-hashes'; | import detectHashes from 'detect-hashes'; | ||||
| import WBCommonFields from 'wb-common-fields'; | import WBCommonFields from 'wb-common-fields'; | ||||
| import WBContainerRequestFields from 'wb-container-request-fields'; | |||||
| class WBProcessView extends Component { | class WBProcessView extends Component { | ||||
| constructor(...args) { | constructor(...args) { | ||||
| @@ -88,8 +89,12 @@ class WBProcessView extends Component { | |||||
| This is the process view for { uuid } | This is the process view for { uuid } | ||||
| </div> | </div> | ||||
| <h2>Common Fields</h2> | |||||
| <WBCommonFields app={ app } uuid={ uuid } /> | <WBCommonFields app={ app } uuid={ uuid } /> | ||||
| <h2>Container Request Fields</h2> | |||||
| <WBContainerRequestFields app={ app } uuid={ uuid } /> | |||||
| <div> | <div> | ||||
| <a href="/browse">Click here</a> | <a href="/browse">Click here</a> | ||||
| </div> | </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'; | import { h, Component } from 'preact'; | ||||
| class WBTable extends Component { | class WBTable extends Component { | ||||
| render({ columns, rows }) { | |||||
| render({ columns, rows, headerClasses }) { | |||||
| return ( | return ( | ||||
| <table class="table table-striped table-hover"> | <table class="table table-striped table-hover"> | ||||
| <thead class="thead-light"> | <thead class="thead-light"> | ||||
| <tr> | <tr> | ||||
| { columns.map(c => <th>{ c }</th>) } | |||||
| { columns.map((c, i) => <th class={ headerClasses[i] }>{ c }</th>) } | |||||
| </tr> | </tr> | ||||
| </thead> | </thead> | ||||
| <tbody> | <tbody> | ||||
| @@ -23,4 +23,8 @@ class WBTable extends Component { | |||||
| } | } | ||||
| } | } | ||||
| WBTable.defaultProps = { | |||||
| 'headerClasses': [] | |||||
| }; | |||||
| export default WBTable; | export default WBTable; | ||||