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