diff --git a/frontend/src/js/arvados/base/url-for-object.js b/frontend/src/js/arvados/base/url-for-object.js
index 8a3e4a9..fce5424 100644
--- a/frontend/src/js/arvados/base/url-for-object.js
+++ b/frontend/src/js/arvados/base/url-for-object.js
@@ -4,9 +4,12 @@ function urlForObject(item, mode='primary') {
let objectType = arvadosTypeName(item.uuid.split('-')[1]);
if (objectType === 'user')
return ('/browse/' + item.uuid);
- else if (objectType === 'group' && item.group_class === 'project')
- return ('/browse/' + item.uuid);
- else if (objectType === 'container_request')
+ else if (objectType === 'group' && item.group_class === 'project') {
+ if (mode === 'properties')
+ return ('/project/' + item.uuid);
+ else
+ return ('/browse/' + item.uuid);
+ } else if (objectType === 'container_request')
return ('/process/' + item.uuid);
else if (objectType === 'workflow') {
if (mode === 'launch')
diff --git a/frontend/src/js/component/wb-json-viewer.js b/frontend/src/js/component/wb-json-viewer.js
index d3940d5..abbf696 100644
--- a/frontend/src/js/component/wb-json-viewer.js
+++ b/frontend/src/js/component/wb-json-viewer.js
@@ -3,58 +3,7 @@ import WBIdTools from 'wb-id-tools';
import urlForObject from 'url-for-object';
import makeArvadosRequest from 'make-arvados-request';
import arvadosObjectName from 'arvados-object-name';
-
-class WBLazyInlineName extends Component {
- componentWillReceiveProps(nextProps) {
- if (nextProps.identifier === this.props.identifier)
- return;
- this.setState({ item: null });
- }
-
- fetchData() {
- const { app, identifier } = this.props;
- const { arvHost, arvToken } = app.state;
- const typeName = WBIdTools.typeName(identifier);
- if (WBIdTools.isPDH(identifier)) {
- const filters = [
- [ 'portable_data_hash', '=', identifier ]
- ];
- let prom = makeArvadosRequest(arvHost, arvToken,
- '/arvados/v1/collections?filters=' + encodeURIComponent(JSON.stringify(filters)));
- prom = prom.then(xhr => this.setState({ item: {
- uuid: xhr.response.items.length > 0 ? xhr.response.items[0].uuid : '',
- name: xhr.response.items.length > 0 ? xhr.response.items[0].name : 'Not Found' +
- ( xhr.response.items_available > 1 ? ' (+' + (xhr.response.items_available - 1) + ' others)' : '' )
- }}));
- return;
- }
- let prom = makeArvadosRequest(arvHost, arvToken,
- '/arvados/v1/' + typeName + 's/' + identifier);
- prom = prom.then(xhr => this.setState({ item: xhr.response }));
- prom = prom.catch(() => this.setState({ item: { name: 'Not Found' }}));
- }
-
- render({ identifier }, { item }) {
- if (item) {
- return (
- { arvadosObjectName(item) }
- );
- }
-
- const typeName = WBIdTools.typeName(identifier);
- const url = (typeName === 'group' ? '/browse/' + identifier :
- typeName === 'collection' ? '/collection-browse/' + identifier :
- urlForObject({ uuid: identifier }));
- return (
-
- { identifier } { e.preventDefault(); this.fetchData(); } }>
-
-
-
- );
- }
-}
+import WBLazyInlineName from 'wb-lazy-inline-name';
function detectIds(value, app) {
const matches = WBIdTools.detectIdentifiers(value);
diff --git a/frontend/src/js/component/wb-lazy-inline-name.js b/frontend/src/js/component/wb-lazy-inline-name.js
new file mode 100644
index 0000000..3b5ec47
--- /dev/null
+++ b/frontend/src/js/component/wb-lazy-inline-name.js
@@ -0,0 +1,59 @@
+import { h, Component } from 'preact';
+import makeArvadosRequest from 'make-arvados-request';
+import WBIdTools from 'wb-id-tools';
+import urlForObject from 'url-for-object';
+import arvadosObjectName from 'arvados-object-name';
+
+class WBLazyInlineName extends Component {
+ componentWillReceiveProps(nextProps) {
+ if (nextProps.identifier === this.props.identifier)
+ return;
+ this.setState({ item: null });
+ }
+
+ fetchData() {
+ const { app, identifier } = this.props;
+ const { arvHost, arvToken } = app.state;
+ const typeName = WBIdTools.typeName(identifier);
+ if (WBIdTools.isPDH(identifier)) {
+ const filters = [
+ [ 'portable_data_hash', '=', identifier ]
+ ];
+ let prom = makeArvadosRequest(arvHost, arvToken,
+ '/arvados/v1/collections?filters=' + encodeURIComponent(JSON.stringify(filters)));
+ prom = prom.then(xhr => this.setState({ item: {
+ uuid: xhr.response.items.length > 0 ? xhr.response.items[0].uuid : '',
+ name: xhr.response.items.length > 0 ? xhr.response.items[0].name : 'Not Found' +
+ ( xhr.response.items_available > 1 ? ' (+' + (xhr.response.items_available - 1) + ' others)' : '' )
+ }}));
+ return;
+ }
+ let prom = makeArvadosRequest(arvHost, arvToken,
+ '/arvados/v1/' + typeName + 's/' + identifier);
+ prom = prom.then(xhr => this.setState({ item: xhr.response }));
+ prom = prom.catch(() => this.setState({ item: { name: 'Not Found' }}));
+ }
+
+ render({ identifier }, { item }) {
+ if (item) {
+ return (
+ { arvadosObjectName(item) }
+ );
+ }
+
+ const typeName = WBIdTools.typeName(identifier);
+ const url = (typeName === 'group' ? '/browse/' + identifier :
+ typeName === 'collection' ? '/collection-browse/' + identifier :
+ urlForObject({ uuid: identifier }));
+ return (
+
+ { identifier } { e.preventDefault(); this.fetchData(); } }>
+
+
+
+ );
+ }
+}
+
+export default WBLazyInlineName;
diff --git a/frontend/src/js/component/wb-project-fields.js b/frontend/src/js/component/wb-project-fields.js
new file mode 100644
index 0000000..f859fcf
--- /dev/null
+++ b/frontend/src/js/component/wb-project-fields.js
@@ -0,0 +1,65 @@
+import { h, Component } from 'preact';
+import WBTable from 'wb-table';
+import makeArvadosRequest from 'make-arvados-request';
+import WBAccordion from 'wb-accordion';
+import WBJsonViewer from 'wb-json-viewer';
+import wbFormatSpecialValue from 'wb-format-special-value';
+import WBLazyInlineName from 'wb-lazy-inline-name';
+import wbFormatDate from 'wb-format-date';
+
+class WBProjectFields extends Component {
+ componentDidMount() {
+ this.prepareRows();
+ }
+
+ componentWillReceiveProps(nextProps) {
+ this.props = nextProps;
+ this.prepareRows();
+ }
+
+ prepareRows() {
+ let { uuid, app } = this.props;
+ let { arvHost, arvToken } = app.state;
+
+ let prom = makeArvadosRequest(arvHost, arvToken,
+ '/arvados/v1/groups/' + uuid);
+
+ prom = prom.then(xhr => {
+ const item = xhr.response;
+
+ const rows = [
+ [ 'Name', wbFormatSpecialValue(item.name) ],
+ [ 'Description', wbFormatSpecialValue(item.description) ],
+ [ 'Properties', (
+