diff --git a/frontend/package.json b/frontend/package.json index 86ac08b..4abf4ac 100755 --- a/frontend/package.json +++ b/frontend/package.json @@ -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", diff --git a/frontend/rollup.config.js b/frontend/rollup.config.js index b1c184b..b250a14 100755 --- a/frontend/rollup.config.js +++ b/frontend/rollup.config.js @@ -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'}), diff --git a/frontend/src/css/index.css b/frontend/src/css/index.css index e69de29..f0cac02 100755 --- a/frontend/src/css/index.css +++ b/frontend/src/css/index.css @@ -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+ */ +} diff --git a/frontend/src/html/index.html b/frontend/src/html/index.html index 8f02e32..79f83d9 100755 --- a/frontend/src/html/index.html +++ b/frontend/src/html/index.html @@ -3,9 +3,11 @@ + + diff --git a/frontend/src/js/component/wb-common-fields.js b/frontend/src/js/component/wb-common-fields.js index 2fd1223..54a6486 100644 --- a/frontend/src/js/component/wb-common-fields.js +++ b/frontend/src/js/component/wb-common-fields.js @@ -69,7 +69,11 @@ class WBCommonFields extends Component { ) ], [ 'Modified by Client', item.modified_by_client_uuid ], - [ 'API Url', 'https://' + app.state.arvHost + '/arvados/v1' + item.href ], + [ 'API Url', ( + + { 'https://' + app.state.arvHost + '/arvados/v1' + item.href } + + ) ], [ 'ETag', item.etag ] ]; this.setState({ 'rows': rows }); @@ -78,13 +82,12 @@ class WBCommonFields extends Component { render({}, { rows }) { return ( -
-

Common Fields

- { rows ? ( - - ) :
Loading...
} -
+ rows ? ( + + ) : ( +
Loading...
+ ) ); } } diff --git a/frontend/src/js/component/wb-container-request-fields.js b/frontend/src/js/component/wb-container-request-fields.js new file mode 100644 index 0000000..d2b500b --- /dev/null +++ b/frontend/src/js/component/wb-container-request-fields.js @@ -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 || ({ String(item.description) }) ], + [ 'Properties', ( + +
{ JSON.stringify(item.properties, null, 2) }
+
+ ) ], + [ 'State', item.state ], + [ 'Requesting Container', ( + + ) ], + [ 'Container', ( + + ) ], + [ 'Container Count Max', item.container_count_max ], + [ 'Mounts', ( + + { Object.keys(item.mounts).map(k => ( +
{ JSON.stringify(item.mounts[k], null, 2) }
+ )) } +
+ ) ], + [ 'Runtime Constraints', ( + +
{ JSON.stringify(item.runtime_constraints, null, 2) }
+
+ ) ], + [ 'Scheduling Parameters', ( + +
{ JSON.stringify(item.scheduling_parameters, null, 2) }
+
+ ) ], + [ 'Container Image', ( + + ) ], + [ 'Environment', ( + +
{ JSON.stringify(item.environment, null, 2) }
+
+ ) ], + [ 'Working Directory', item.cwd ], + [ 'Command', ( +
{ JSON.stringify(item.command) }
+ ) ], + [ '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', ( + + ) ], + [ 'Output', ( + + ) ], + [ 'Filters', ( + item.filters ? (
{ item.filters }
) : ({ String(item.filters) }) + ) ], + [ 'Runtime Token', item.runtime_token || ({ String(item.runtime_token) }) ], + [ 'Runtime User', ( + + ) ], + [ 'Runtime Auth Scopes', ( + item.runtime_auth_scopes ? ( +
{ JSON.stringify(item.runtime_auth_scopes, null, 2) }
+ ) : ( + { String(item.runtime_auth_scopes) } + ) + ) ] + ]; + this.setState({ 'rows': rows }); + }); + } + + render({}, { rows }) { + return ( + rows ? ( + + ) : ( +
Loading...
+ ) + ); + } +} + +export default WBContainerRequestFields; diff --git a/frontend/src/js/component/wb-name-and-uuid.js b/frontend/src/js/component/wb-name-and-uuid.js new file mode 100644 index 0000000..d3d414d --- /dev/null +++ b/frontend/src/js/component/wb-name-and-uuid.js @@ -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 ( +
{ String(uuid) }
+ ); + + return ( +
+
+ { item ? ( + { arvadosObjectName(item) } + ) : 'Loading...' } +
+
+ { uuid } +
+
+ ); + } +} + +export default WBNameAndUuid; diff --git a/frontend/src/js/misc/arvados-object-name.js b/frontend/src/js/misc/arvados-object-name.js index ba670ec..7aad7fd 100644 --- a/frontend/src/js/misc/arvados-object-name.js +++ b/frontend/src/js/misc/arvados-object-name.js @@ -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; } diff --git a/frontend/src/js/misc/arvados-type-name.js b/frontend/src/js/misc/arvados-type-name.js index af77515..f547dc1 100644 --- a/frontend/src/js/misc/arvados-type-name.js +++ b/frontend/src/js/misc/arvados-type-name.js @@ -9,6 +9,9 @@ const typeIdToName = { }; function arvadosTypeName(id) { + if (!id) + return; + if (id.length === 5) return typeIdToName[id]; else diff --git a/frontend/src/js/page/wb-process-view.js b/frontend/src/js/page/wb-process-view.js index 1c65a80..254635f 100644 --- a/frontend/src/js/page/wb-process-view.js +++ b/frontend/src/js/page/wb-process-view.js @@ -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 } +

Common Fields

+

Container Request Fields

+ + diff --git a/frontend/src/js/widget/wb-accordion.js b/frontend/src/js/widget/wb-accordion.js new file mode 100644 index 0000000..ca44a0b --- /dev/null +++ b/frontend/src/js/widget/wb-accordion.js @@ -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 ( +
+ { children.map((_, i) => ( +
+
+

+ +

+
+ +
+
+ { children[i] } +
+
+
+ )) } +
+ ); + } +}; + +WBAccordion.defaultProps = { + 'cardHeaderClass': 'card-header' +}; + +export default WBAccordion; diff --git a/frontend/src/js/widget/wb-table.js b/frontend/src/js/widget/wb-table.js index c698135..fda9bcf 100644 --- a/frontend/src/js/widget/wb-table.js +++ b/frontend/src/js/widget/wb-table.js @@ -1,12 +1,12 @@ import { h, Component } from 'preact'; class WBTable extends Component { - render({ columns, rows }) { + render({ columns, rows, headerClasses }) { return ( - { columns.map(c => ) } + { columns.map((c, i) => ) } @@ -23,4 +23,8 @@ class WBTable extends Component { } } +WBTable.defaultProps = { + 'headerClasses': [] +}; + export default WBTable;
{ c }{ c }