@@ -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; |