Commit 003809cc authored by Markus Scheidgen's avatar Markus Scheidgen
Browse files

Updated react scripts. Minor fixes. Added overview figure. #233

parent a420d4bc
Pipeline #74237 failed with stages
in 6 minutes and 37 seconds
...@@ -4,6 +4,12 @@ ...@@ -4,6 +4,12 @@
"commit": "e98694e", "commit": "e98694e",
"private": true, "private": true,
"dependencies": { "dependencies": {
"@testing-library/jest-dom": "^4.2.4",
"@testing-library/react": "^9.3.2",
"@testing-library/user-event": "^7.1.2",
"react": "^16.13.1",
"react-dom": "^16.13.1",
"react-scripts": "3.4.1",
"@material-ui/core": "^4.0.0", "@material-ui/core": "^4.0.0",
"@material-ui/icons": "^4.0.0", "@material-ui/icons": "^4.0.0",
"@material-ui/lab": "^4.0.0-alpha.49", "@material-ui/lab": "^4.0.0-alpha.49",
...@@ -26,19 +32,16 @@ ...@@ -26,19 +32,16 @@
"pace-js": "^1.0.2", "pace-js": "^1.0.2",
"piwik-react-router": "^0.12.1", "piwik-react-router": "^0.12.1",
"qs": "^6.8.0", "qs": "^6.8.0",
"react": "^16.8.0",
"react-app-polyfill": "^1.0.1", "react-app-polyfill": "^1.0.1",
"react-autosuggest": "^9.4.3", "react-autosuggest": "^9.4.3",
"react-cookie": "^3.0.8", "react-cookie": "^3.0.8",
"react-copy-to-clipboard": "^5.0.1", "react-copy-to-clipboard": "^5.0.1",
"react-dom": "^16.8.0",
"react-dropzone": "^5.0.1", "react-dropzone": "^5.0.1",
"react-highlight": "^0.12.0", "react-highlight": "^0.12.0",
"react-infinite-scroller": "^1.2.4", "react-infinite-scroller": "^1.2.4",
"react-json-view": "^1.19.1", "react-json-view": "^1.19.1",
"react-keycloak": "^6.1.0", "react-keycloak": "^6.1.0",
"react-router-dom": "^5.1.2", "react-router-dom": "^5.1.2",
"react-scripts": "1.1.4",
"react-swipeable-views": "^0.13.0", "react-swipeable-views": "^0.13.0",
"recompose": "^0.28.2", "recompose": "^0.28.2",
"swagger-client": "^3.8.22", "swagger-client": "^3.8.22",
...@@ -51,13 +54,13 @@ ...@@ -51,13 +54,13 @@
"prebuild": "npm run generate-build-version", "prebuild": "npm run generate-build-version",
"start": "react-scripts start", "start": "react-scripts start",
"build": "react-scripts build", "build": "react-scripts build",
"test": "react-scripts test --env=jsdom", "test": "react-scripts test",
"eject": "react-scripts eject" "eject": "react-scripts eject"
}, },
"devDependencies": { "devDependencies": {
"@material-ui/codemod": "^4.5.0", "@material-ui/codemod": "^4.5.0",
"babel-eslint": "^8.2.6", "babel-eslint": "^10.1.0",
"eslint": "^4.19.1", "eslint": "^6.6.0",
"eslint-config-standard": "^11.0.0", "eslint-config-standard": "^11.0.0",
"eslint-plugin-import": "^2.14.0", "eslint-plugin-import": "^2.14.0",
"eslint-plugin-node": "^8.0.1", "eslint-plugin-node": "^8.0.1",
...@@ -67,5 +70,20 @@ ...@@ -67,5 +70,20 @@
"react-docgen": "^5.3.0", "react-docgen": "^5.3.0",
"serve": "^10.0.0" "serve": "^10.0.0"
}, },
"eslintConfig": {
"extends": "react-app"
},
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
},
"homepage": "http://example.com/fairdi/nomad/latest/gui" "homepage": "http://example.com/fairdi/nomad/latest/gui"
} }
import React from 'react' import React, { useContext, useLayoutEffect, useRef, useCallback, useEffect } from 'react'
import {ReactComponent as AboutSvg} from './about.svg'
import PropTypes from 'prop-types' import PropTypes from 'prop-types'
import { withStyles } from '@material-ui/core/styles'
import Markdown from './Markdown' import Markdown from './Markdown'
import { appBase, optimadeBase, apiBase, debug, consent } from '../config' import { appBase, optimadeBase, apiBase, debug, consent } from '../config'
import { compose } from 'recompose' import { apiContext } from './api'
import { withApi } from './api'
import packageJson from '../../package.json' import packageJson from '../../package.json'
import { domains } from './domains' import { domains } from './domains'
import { Grid, Card, CardContent, Typography, makeStyles, Link } from '@material-ui/core'
import { Link as RouterLink, useHistory } from 'react-router-dom'
class About extends React.Component { const useCardStyles = makeStyles(theme => ({
static propTypes = { title: {
classes: PropTypes.object.isRequired, marginBottom: theme.spacing(1)
info: PropTypes.object,
raiseError: PropTypes.func.isRequired
} }
}))
static styles = theme => ({ function MarkdownCard({title, children, xs, top, bottom}) {
const classes = useCardStyles()
const style = {}
if (top) {
style['paddingBottom'] = 0
}
if (bottom) {
style['paddingTop'] = 0
}
return <Grid item xs={xs} style={style}>
<Card>
<CardContent>
<Typography variant="h6" className={classes.title}>{title}</Typography>
<Typography>{children}</Typography>
</CardContent>
</Card>
</Grid>
}
MarkdownCard.propTypes = {
title: PropTypes.string.isRequired,
children: PropTypes.node,
xs: PropTypes.number,
top: PropTypes.bool,
bottom: PropTypes.bool
}
const useStyles = makeStyles(theme => ({
root: { root: {
padding: theme.spacing(3) padding: theme.spacing(3)
},
container: {
maxWidth: 1024,
margin: 'auto',
width: '100%'
} }
}))
export default function About() {
const classes = useStyles()
const {info} = useContext(apiContext)
const svg = useRef()
const history = useHistory()
const makeClickable = useCallback((id, onClick) => {
const element = svg.current.querySelector('#' + id)
element.style.cursor = 'pointer'
element.firstChild.onclick = () => {
onClick()
}
}, [svg])
const setText = useCallback((id, lines) => {
const element = svg.current.querySelector('#' + id)
const x = element.getAttribute('x')
element.innerHTML = lines.map((line, i) => `<tspan x="${x}" dy="${i === 0 ? '0' : '1.2em'}">${line}</tspan>`).join('')
}, [svg])
useLayoutEffect(() => {
makeClickable('upload', () => {
history.push('/upload')
}) })
makeClickable('encyclopedia', () => {
window.location.href = 'https://encyclopedia.nomad-coe.eu/gui/#/search'
})
makeClickable('search', () => {
history.push('/search')
})
}, [svg])
render() { useEffect(() => {
const { classes, info } = this.props const statistics = (info && info.statistics) || {}
const value = (key, unit) => {
const nominal = statistics[key]
let stringValue = null
if (nominal) {
if (nominal >= 1.0e+9) {
stringValue = Math.floor(nominal / 1.0e+9) + ' bln.'
} else if (nominal >= 1.0e+6) {
stringValue = Math.floor(nominal / 1.0e+6) + ' mln.'
} else {
stringValue = Math.floor(nominal / 1.0e+3) + ' tsd.'
}
return `${stringValue || '...'} ${unit}`
}
}
setText('repositoryStats', [
value('n_entries', 'entries'),
value('n_uploads', 'uploads')
])
setText('archiveStats', [
value('n_calculations', 'results'),
value('n_quantities', 'quantities')
])
}, [svg, info])
return ( return <div className={classes.root}>
<div className={classes.root}> <Grid className={classes.container} container spacing={2}>
<Grid item xs={12}>
<Markdown>{` <Markdown>{`
# The NOMAD Repository and Archive # The NOMAD Repository and Archive
...@@ -36,12 +123,80 @@ class About extends React.Component { ...@@ -36,12 +123,80 @@ class About extends React.Component {
and how to prepare your data on the [NOMAD Repository homepage](https://repository.nomad-coe.eu/). and how to prepare your data on the [NOMAD Repository homepage](https://repository.nomad-coe.eu/).
You can access all published data without an account. If you want to provide You can access all published data without an account. If you want to provide
your own data, please login or register for an account. your own data, please login or register for an account.
`}</Markdown>
</Grid>
<MarkdownCard xs={6} title="Interactive Search" top>
NOMAD extracts <b>rich metadata</b> from uploaded raw-data. <Link component={RouterLink} to={'/search'}>
Explore NOMAD&apos;s data</Link> by creating complex queries from interactive data visualizations of key
properties, including the simulated composition/system, used method, upload metadata,
as well as material classifications and available quantities. Or use
the <b>Optimade</b> filter language to add arbitrarily nested queries.
</MarkdownCard>
<MarkdownCard xs={6} title="A common data format" top>
The <b>NOMAD Archive</b> provides data in processed and normalized form in a machine processable and common hierarchical format.
All data in the NOMAD Archive is organized into nested sections of quantities with well defined units,
data types, shapes, and descriptions. These definitions are called the <b>NOMAD Metainfo</b> and they
can be <Link component={RouterLink} to={'/metainfo'}>browsed here</Link>.
</MarkdownCard>
<Grid item xs={12} style={{paddingTop: 0, paddingBottom: 0}}>
<AboutSvg ref={svg}></AboutSvg>
</Grid>
<MarkdownCard xs={4} title="Uploading is simple" bottom>
<p>
You provide your own data <i>as is</i>. Just zip your code input and out files as they are,
including nested directory structures and potential auxiliary files, and upload
up to 32GB in a single .zip or .tar(.gz) file. NOMAD will automatically discover
and process the relevant files.
</p>
<p>
You can <b>privately</b> inspect, curate, or delete your data before publishing.
Data can be published with an <b>embargo (up to 3 years)</b> to only share data with
selected users.
</p>
<p>
Add additional metadata like <b>comments</b>, <b>references</b> to websites or papers, and your
<b>co-authors</b>. Curate your uploaded code runs into larger <b>datasets</b> and cite your data with a <b>DOI</b>
that we provide on request.
</p>
<p>
You can provide via GUI or shell command <Link component={RouterLink} to={'/uploads'}>here</Link>.
Manage already uploaded data <Link component={RouterLink} to={'/userdata'}>here</Link>.
</p>
</MarkdownCard>
<MarkdownCard xs={4} title="Processing" bottom>
<p>
Uploaded data is automatically processed and made available
in the uploaded <b>raw files</b> or in its processed and unified <b>Archive</b> form.
NOMAD parsers convert raw code input and output files into NOMAD&apos;s common data format.
You can inspect the Archive form and extracted metadata before
publishing your data.
</p>
<p>NOMAD supports most community codes: {info ? info.codes.join(', ') : '...'}</p>
<p>
To use NOMAD&apos;s parsers and normalizers outside of NOMAD.
Read <Link href="">here</Link> on how to install
our software and how to use NOMAD processing in your Python environment.
</p>
</MarkdownCard>
<MarkdownCard xs={4} title="APIs" bottom><Markdown>{`
The NOMAD can also be accessed programmatically via ReST APIs.
There is the proprietary NOMAD API and an implementation of the
standardized [OPTiMaDe API (0.10.0)](https://github.com/Materials-Consortia/OPTiMaDe/tree/master)
materials science database API.
In the future, this web-page will include more and more features of other NOMAD Both APIs are described via [swagger/OpenAPI spec.](https://swagger.io/),
components as an effort to consolidate the various web applications from the therefore you can use your favorite swagger client library
NOMAD Repository, Archive, Metainfo, Encyclopedia, and Analytics Toolkit. (e.g. [bravado](https://github.com/Yelp/bravado) for Python):
- [NOMAD API](${apiBase}/)
- [OPTiMaDe API](${optimadeBase}/)
There is a [tutorial on how to use the API with plain Python](${appBase}/docs/api_tutorial.html).
Another [tutorial covers how to install and use NOMAD's Python client library](${appBase}/docs/archive_tutorial.html).
The [NOMAD Analytics Toolkit](https://analytic-toolkit.nomad-coe.eu) allows to use
this without installation and directly on NOMAD servers.
`}</Markdown></MarkdownCard>
<Grid item xs={12}>
<Markdown>{`
### Getting Help ### Getting Help
If you encounter any difficulties, please write to If you encounter any difficulties, please write to
[webmaster@nomad-coe.eu](mailto:webmaster@nomad-coe.eu). If you think [webmaster@nomad-coe.eu](mailto:webmaster@nomad-coe.eu). If you think
...@@ -49,40 +204,6 @@ class About extends React.Component { ...@@ -49,40 +204,6 @@ class About extends React.Component {
about possible features, feel free to open an issue on our [issue tracking about possible features, feel free to open an issue on our [issue tracking
system](https://gitlab.mpcdf.mpg.de/nomad-lab/nomad-FAIR/issues). system](https://gitlab.mpcdf.mpg.de/nomad-lab/nomad-FAIR/issues).
### APIs
The NOMAD Repository and Archive can also be accessed programmatically via ReST APIs.
There is the proprietary NOMAD API and an implementation of the
[OPTiMaDe API (0.10.0)](https://github.com/Materials-Consortia/OPTiMaDe/tree/master)
standardized by the [OPTiMaDe consortium](https://www.optimade.org/)
Both APIs are described via [swagger](https://swagger.io/) (also known as OpenAPI spec.),
therefore you can use your favorite swagger client library
(e.g. [bravado](https://github.com/Yelp/bravado) for Python).
There are also web-based GUIs that allow to explore the APIs and their documentation:
- [NOMAD API](${apiBase}/)
- [OPTiMaDe API](${optimadeBase}/)
There is a [tutorial on how to use the API with Python](${appBase}/docs/api_tutorial.html).
There is also a Python library. You can use *pip* to install the library.
\`\`\`
pip install ${appBase}/dist/nomad-0.8.0.tar.gz
\`\`\`
The NOMAD Archive uses data that adheres to formal data definitions that we call
the NOMAD Metainfo. You can download these definition in their [JSON form
here](${apiBase}/archive/metainfo/all.nomadmetainfo.json). Otherwise, you
can use the Meta Info browser from the menu to explore.
${debug ? `
### Material science data and domains
Originally NOMAD was build for DFT calculations and data from the respective
community code. By NOMAD supports multiple materials science domains:
${info && info.domains.map(domain => domains[domain.name]).map(domain => `- ${domain.name}: ${domain.about}`).join('\n')}
` : ''}
### Developer Documentation ### Developer Documentation
The [in-depth developer documentation](${appBase}/docs/index.html) The [in-depth developer documentation](${appBase}/docs/index.html)
contains a general introduction to NOMAD, the underlying architecture, contains a general introduction to NOMAD, the underlying architecture,
...@@ -96,6 +217,14 @@ class About extends React.Component { ...@@ -96,6 +217,14 @@ class About extends React.Component {
To push code, you need an MPCDF account and you can apply To push code, you need an MPCDF account and you can apply
[here](https://www.mpcdf.mpg.de/userspace/forms/onlineregistrationform). [here](https://www.mpcdf.mpg.de/userspace/forms/onlineregistrationform).
${debug ? `
### Material science data and domains
Originally NOMAD was build for DFT calculations and data from the respective
community code. By NOMAD supports multiple materials science domains:
${info && info.domains.map(domain => domains[domain.name]).map(domain => `- ${domain.name}: ${domain.about}`).join('\n')}
` : ''}
${debug ? ` ${debug ? `
### Log management with Elastic stack ### Log management with Elastic stack
We use a central logging system based on the *elastic*-stack We use a central logging system based on the *elastic*-stack
...@@ -125,9 +254,7 @@ class About extends React.Component { ...@@ -125,9 +254,7 @@ class About extends React.Component {
- parsers: ${info ? info.parsers.join(', ') : 'loading'} - parsers: ${info ? info.parsers.join(', ') : 'loading'}
- normalizers: ${info ? info.normalizers.join(', ') : 'loading'} - normalizers: ${info ? info.normalizers.join(', ') : 'loading'}
`}</Markdown> `}</Markdown>
</Grid>
</Grid>
</div> </div>
)
}
} }
export default compose(withApi(), withStyles(About.styles))(About)
...@@ -5,7 +5,7 @@ import PropTypes, { instanceOf } from 'prop-types' ...@@ -5,7 +5,7 @@ import PropTypes, { instanceOf } from 'prop-types'
import { compose } from 'recompose' import { compose } from 'recompose'
import classNames from 'classnames' import classNames from 'classnames'
import { MuiThemeProvider, withStyles, makeStyles } from '@material-ui/core/styles' import { MuiThemeProvider, withStyles, makeStyles } from '@material-ui/core/styles'
import { LinearProgress, ListItemIcon, ListItemText, MenuList, MenuItem, Typography, import { LinearProgress, MenuList, Typography,
AppBar, Toolbar, Button, DialogContent, DialogTitle, DialogActions, Dialog, Tooltip, AppBar, Toolbar, Button, DialogContent, DialogTitle, DialogActions, Dialog, Tooltip,
Snackbar, SnackbarContent } from '@material-ui/core' Snackbar, SnackbarContent } from '@material-ui/core'
import { Route, Link, withRouter, useLocation } from 'react-router-dom' import { Route, Link, withRouter, useLocation } from 'react-router-dom'
...@@ -183,7 +183,7 @@ class NavigationUnstyled extends React.Component { ...@@ -183,7 +183,7 @@ class NavigationUnstyled extends React.Component {
marginRight: 0 marginRight: 0
}, },
divider: { divider: {
flexGrow: 1 width: theme.spacing(3)
} }
}) })
...@@ -301,9 +301,15 @@ class NavigationUnstyled extends React.Component { ...@@ -301,9 +301,15 @@ class NavigationUnstyled extends React.Component {
tooltip="Manage your data" tooltip="Manage your data"
icon={<UserDataIcon/>} icon={<UserDataIcon/>}
/> />
<MainMenuItem
title="Meta Info"
path="/metainfo"
tooltip="Browse the archive schema"
icon={<MetainfoIcon/>}
/>
<div className={classes.divider} /> <div className={classes.divider} />
<MainMenuItem <MainMenuItem
title="Overview" title="About"
path="/" path="/"
tooltip="NOMAD Repository and Archive" tooltip="NOMAD Repository and Archive"
icon={<AboutIcon/>} icon={<AboutIcon/>}
...@@ -314,12 +320,6 @@ class NavigationUnstyled extends React.Component { ...@@ -314,12 +320,6 @@ class NavigationUnstyled extends React.Component {
tooltip="Frequently Asked Questions (FAQ)" tooltip="Frequently Asked Questions (FAQ)"
icon={<FAQIcon/>} icon={<FAQIcon/>}
/> />
<MainMenuItem
title="Meta Info"
path="/metainfo"
tooltip="Browse the archive schema"
icon={<MetainfoIcon/>}
/>
</MenuList> </MenuList>
<LoadingIndicator /> <LoadingIndicator />
</AppBar> </AppBar>
......
...@@ -12,6 +12,11 @@ class FAQ extends React.Component { ...@@ -12,6 +12,11 @@ class FAQ extends React.Component {
static styles = theme => ({ static styles = theme => ({
root: { root: {
padding: theme.spacing(3) padding: theme.spacing(3)
},
container: {
maxWidth: 1024,
margin: 'auto',
width: '100%'
} }
}) })
...@@ -19,8 +24,8 @@ class FAQ extends React.Component { ...@@ -19,8 +24,8 @@ class FAQ extends React.Component {
const { classes } = this.props const { classes } = this.props
return ( return (
<div className={classes.root}> <div className={classes.root}><div className={classes.container}>
<Markdown>{` <Markdown >{`
# Frequently Asked Questions (FAQ) # Frequently Asked Questions (FAQ)
These are often repeated questions that cover the basic NOMAD use-cases. If you have These are often repeated questions that cover the basic NOMAD use-cases. If you have
...@@ -160,7 +165,7 @@ class FAQ extends React.Component { ...@@ -160,7 +165,7 @@ class FAQ extends React.Component {
write us an Email ([${email}](mailto:${email})) and we will figure out if and how write us an Email ([${email}](mailto:${email})) and we will figure out if and how
to support this code in the future. to support this code in the future.
`}</Markdown> `}</Markdown>
</div> </div></div>
) )
} }
} }
......
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg width="100%" height="100%" viewBox="0 0 1025 632" version="1.1" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:1.5;">
<g id="Artboard1" transform="matrix(1.00641,0,0,1.03856,-2.66723,0)">
<rect x="2.65" y="0" width="1018.16" height="608.36" style="fill:none;"/>
<clipPath id="_clip1">
<rect x="2.65" y="0" width="1018.16" height="608.36"/>
</clipPath>
<g clip-path="url(#_clip1)">
<g transform="matrix(1,0,0,0.964223,9.59233e-14,12.4654)">
<g transform="matrix(0.993631,0,0,0.998598,2.65025,0)">
<path d="M30.051,395.2L9,382.85L9,-9.946" style="fill:none;stroke:black;stroke-width:0.5px;"/>
</g>
<g transform="matrix(0.993631,0,0,0.998598,2.65025,0)">
<path d="M570.5,154.635L570.5,-9.946" style="fill:none;stroke:black;stroke-width:0.5px;"/>
</g>
<g transform="matrix(0.993631,0,0,0.998598,2.65025,0)">
<path d="M173.761,518.483L173.761,615.873" style="fill:none;stroke:black;stroke-width:0.5px;"/>
</g>
<g transform="matrix(0.993631,0,0,0.998598,2.65025,0)">
<path d="M476.136,344.02L551.24,386.958L551.24,615.873" style="fill:none;stroke:black;stroke-width:0.5px;"/>
</g>
<g transform="matrix(0.993631,0,0,0.998598,2.65025,0)">
<path d="M884.281,286.732L855.686,302.319L855.686,615.873" style="fill:none;stroke:black;stroke-width:0.5px;"/>
</g>
</g>
<g transform="matrix(2.40523,0,0,2.33077,-418.948,-98.2253)">
<path d="M247.871,178.885L278.855,196.774" style="fill:none;stroke:black;stroke-width:2.07px;"/>
</g>
<g transform="matrix(2.40523,0,0,2.33077,-418.948,-98.2253)">
<path d="M247.871,214.663L309.839,250.44L371.806,214.663L371.806,207.187L309.839,171.41L247.871,207.187L247.871,214.663Z" style="fill:rgb(123,31,162);"/>
</g>
<g transform="matrix(0.993631,0,0,0.962871,2.65025,0)">
<path d="M33.307,410.333L475.711,154.911L554.127,200.185L551.627,204.515L475.711,160.685L38.307,413.22L33.307,410.333Z"/>
</g>
<g transform="matrix(2.40523,0,0,2.33077,-103.005,-255.503)">
<path d="M327.372,177.707L358.588,195.461L373.365,186.93L369.268,171.406L342.381,169.042L327.372,177.707Z" style="fill:white;stroke:black;stroke-width:0.83px;stroke-linecap:square;stroke-linejoin:miter;"/>
</g>
<g transform="matrix(2.40523,0,0,2.33077,-401.534,-88.4828)">
<path d="M327.35,177.72L358.566,195.474L395.044,174.413L390.948,158.89L364.06,156.525L327.35,177.72Z" style="fill:white;stroke:black;stroke-width:0.83px;stroke-linecap:square;stroke-linejoin:miter;"/>
</g>
<g transform="matrix(2.40523,0,0,2.33077,-699.627,78.2935)">
<path d="M325.331,178.885L356.547,196.64L395.044,174.413L390.948,158.89L364.06,156.525L325.331,178.885Z" style="fill:white;stroke:black;stroke-width:0.83px;stroke-linecap:square;stroke-linejoin:miter;"/>
</g>
<g transform="matrix(0.993631,0,0,0.962871,2.65025,0)">
<path d="M858.552,280.292L863.552,283.179L475.711,507.099L397.295,461.826L399.795,457.496L475.711,501.326L858.552,280.292Z"/>
</g>
<g transform="matrix(2.40523,0,0,2.33077,-418.948,-98.2253)">
<path d="M453.475,154.483L495.742,178.885" style="fill:none;stroke:black;stroke-width:2.07px;stroke-linecap:butt;stroke-linejoin:miter;"/>
</g>
<g transform="matrix(2.40523,0,0,2.33077,-418.948,-98.2253)">
<path d="M371.806,143.108L433.774,178.885L495.742,143.108L495.742,135.633L433.774,99.856L371.806,135.633L371.806,143.108Z" style="fill:rgb(0,121,107);"/>
</g>
<g transform="matrix(2.40523,0,0,2.33077,-171.125,-236.877)">
<path d="M371.806,143.108L433.774,178.885L495.742,143.108L433.774,107.331L371.806,143.108Z" style="fill:white;"/>
<path d="M371.806,143.108L433.774,178.885L495.742,143.108L433.774,107.331L371.806,143.108ZM374.698,143.108L433.774,177.216L492.85,143.108L433.774,109.001L374.698,143.108Z" style="fill:rgb(255,160,0);"/>
</g>
<g transform="matrix(1.44927,-0.810834,1.67347,0.93627,-632.38,373.612)">
<text x="280px" y="325.975px" style="font-family:'TitilliumWeb-Bold', 'Titillium Web';font-weight:700;font-size:14.25px;fill:rgb(235,235,235);">repository</text>
</g>
<g transform="matrix(1.44927,-0.810834,1.67347,0.93627,-736.377,315.428)">
<text id="repositoryStats" x="280px" y="322.821px" style="font-family:'TitilliumWeb-Regular', 'Titillium Web';font-size:9.5px;fill:rgb(235,235,235);">repository stats</text>
</g>
<g transform="matrix(1.44927,-0.810834,1.67347,0.93627,-437.669,148.307)">
<text id="archiveStats" x="280px" y="322.821px" style="font-family:'TitilliumWeb-Regular', 'Titillium Web';font-size:9.5px;fill:rgb(235,235,235);">archiv<tspan x="303.76px " y="322.821px ">e</tspan> stats</text>
</g>
<g transform="matrix(1.44927,-0.810834,1.67347,0.93627,-516.391,267.591)">
<text x="280px" y="324.526px" style="font-family:'TitilliumWeb-Regular', 'Titillium Web';font-size:11.875px;">pr<tspan x="290.248px " y="324.526px ">o</tspan>cessing</text>
</g>
<g transform="matrix(1.44927,-0.810834,1.67347,0.93627,-815.212,434.774)">
<text x="280px" y="324.526px" style="font-family:'TitilliumWeb-Regular', 'Titillium Web';font-size:11.875px;">upload</text>
</g>
<g transform="matrix(1.44927,-0.810834,1.67347,0.93627,-917.772,312.429)">
<text x="280px" y="324.526px" style="font-family:'TitilliumWeb-Regular', 'Titillium Web';font-size:11.875px;">explor<tspan x="310.578px " y="324.526px "&g