repo details page

Signed-off-by: Raul Kele <raulkeleblk@gmail.com>
This commit is contained in:
Raul Kele 2022-07-11 16:35:15 +03:00 committed by Ramkumar Chinchani
parent 8f91259e4e
commit a84ad70fa2
17 changed files with 941 additions and 253 deletions

359
package-lock.json generated

@ -12,6 +12,7 @@
"@emotion/styled": "^11.9.3",
"@mui-treasury/styles": "^1.13.1",
"@mui/icons-material": "^5.2.5",
"@mui/lab": "^5.0.0-alpha.89",
"@mui/material": "^5.8.6",
"@mui/styles": "^5.8.6",
"@testing-library/jest-dom": "^5.16.1",
@ -1933,6 +1934,75 @@
"postcss": "^8.3"
}
},
"node_modules/@date-io/core": {
"version": "2.14.0",
"resolved": "https://registry.npmjs.org/@date-io/core/-/core-2.14.0.tgz",
"integrity": "sha512-qFN64hiFjmlDHJhu+9xMkdfDG2jLsggNxKXglnekUpXSq8faiqZgtHm2lsHCUuaPDTV6wuXHcCl8J1GQ5wLmPw=="
},
"node_modules/@date-io/date-fns": {
"version": "2.14.0",
"resolved": "https://registry.npmjs.org/@date-io/date-fns/-/date-fns-2.14.0.tgz",
"integrity": "sha512-4fJctdVyOd5cKIKGaWUM+s3MUXMuzkZaHuTY15PH70kU1YTMrCoauA7hgQVx9qj0ZEbGrH9VSPYJYnYro7nKiA==",
"dependencies": {
"@date-io/core": "^2.14.0"
},
"peerDependencies": {
"date-fns": "^2.0.0"
},
"peerDependenciesMeta": {
"date-fns": {
"optional": true
}
}
},
"node_modules/@date-io/dayjs": {
"version": "2.14.0",
"resolved": "https://registry.npmjs.org/@date-io/dayjs/-/dayjs-2.14.0.tgz",
"integrity": "sha512-4fRvNWaOh7AjvOyJ4h6FYMS7VHLQnIEeAV5ahv6sKYWx+1g1UwYup8h7+gPuoF+sW2hTScxi7PVaba2Jk/U8Og==",
"dependencies": {
"@date-io/core": "^2.14.0"
},
"peerDependencies": {
"dayjs": "^1.8.17"
},
"peerDependenciesMeta": {
"dayjs": {
"optional": true
}
}
},
"node_modules/@date-io/luxon": {
"version": "2.14.0",
"resolved": "https://registry.npmjs.org/@date-io/luxon/-/luxon-2.14.0.tgz",
"integrity": "sha512-KmpBKkQFJ/YwZgVd0T3h+br/O0uL9ZdE7mn903VPAG2ZZncEmaUfUdYKFT7v7GyIKJ4KzCp379CRthEbxevEVg==",
"dependencies": {
"@date-io/core": "^2.14.0"
},
"peerDependencies": {
"luxon": "^1.21.3 || ^2.x"
},
"peerDependenciesMeta": {
"luxon": {
"optional": true
}
}
},
"node_modules/@date-io/moment": {
"version": "2.14.0",
"resolved": "https://registry.npmjs.org/@date-io/moment/-/moment-2.14.0.tgz",
"integrity": "sha512-VsoLXs94GsZ49ecWuvFbsa081zEv2xxG7d+izJsqGa2L8RPZLlwk27ANh87+SNnOUpp+qy2AoCAf0mx4XXhioA==",
"dependencies": {
"@date-io/core": "^2.14.0"
},
"peerDependencies": {
"moment": "^2.24.0"
},
"peerDependenciesMeta": {
"moment": {
"optional": true
}
}
},
"node_modules/@emotion/babel-plugin": {
"version": "11.7.2",
"resolved": "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.7.2.tgz",
@ -3061,6 +3131,97 @@
}
}
},
"node_modules/@mui/lab": {
"version": "5.0.0-alpha.89",
"resolved": "https://registry.npmjs.org/@mui/lab/-/lab-5.0.0-alpha.89.tgz",
"integrity": "sha512-u5bMi/V+Utwouo9awVzGasj/LudlRqPFyMo2L5/y60uFo0EaG17bt1jh/U7smQCdjd+7tvJ39HNMkEmIoGr7BQ==",
"dependencies": {
"@babel/runtime": "^7.17.2",
"@mui/base": "5.0.0-alpha.88",
"@mui/system": "^5.8.7",
"@mui/utils": "^5.8.6",
"@mui/x-date-pickers": "5.0.0-alpha.1",
"clsx": "^1.2.0",
"prop-types": "^15.8.1",
"react-is": "^17.0.2",
"react-transition-group": "^4.4.2",
"rifm": "^0.12.1"
},
"engines": {
"node": ">=12.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/mui"
},
"peerDependencies": {
"@emotion/react": "^11.5.0",
"@emotion/styled": "^11.3.0",
"@mui/material": "^5.0.0",
"@types/react": "^17.0.0 || ^18.0.0",
"date-fns": "^2.25.0",
"dayjs": "^1.10.7",
"luxon": "^1.28.0 || ^2.0.0",
"moment": "^2.29.1",
"react": "^17.0.0 || ^18.0.0",
"react-dom": "^17.0.0 || ^18.0.0"
},
"peerDependenciesMeta": {
"@emotion/react": {
"optional": true
},
"@emotion/styled": {
"optional": true
},
"@types/react": {
"optional": true
},
"date-fns": {
"optional": true
},
"dayjs": {
"optional": true
},
"luxon": {
"optional": true
},
"moment": {
"optional": true
}
}
},
"node_modules/@mui/lab/node_modules/@mui/base": {
"version": "5.0.0-alpha.88",
"resolved": "https://registry.npmjs.org/@mui/base/-/base-5.0.0-alpha.88.tgz",
"integrity": "sha512-uL7ej2F/3GUnZewsDQSHUVHoSBT3AQcTIdfdy6QeCHy7X26mtbcIvTRcjl2PzbbNQplppavSTibPiQG/giJ+ng==",
"dependencies": {
"@babel/runtime": "^7.17.2",
"@emotion/is-prop-valid": "^1.1.3",
"@mui/types": "^7.1.4",
"@mui/utils": "^5.8.6",
"@popperjs/core": "^2.11.5",
"clsx": "^1.2.0",
"prop-types": "^15.8.1",
"react-is": "^17.0.2"
},
"engines": {
"node": ">=12.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/mui"
},
"peerDependencies": {
"@types/react": "^17.0.0 || ^18.0.0",
"react": "^17.0.0 || ^18.0.0",
"react-dom": "^17.0.0 || ^18.0.0"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
}
}
},
"node_modules/@mui/material": {
"version": "5.8.6",
"resolved": "https://registry.npmjs.org/@mui/material/-/material-5.8.6.tgz",
@ -3131,12 +3292,13 @@
}
},
"node_modules/@mui/styled-engine": {
"version": "5.8.0",
"resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-5.8.0.tgz",
"integrity": "sha512-Q3spibB8/EgeMYHc+/o3RRTnAYkSl7ROCLhXJ830W8HZ2/iDiyYp16UcxKPurkXvLhUaILyofPVrP3Su2uKsAw==",
"version": "5.8.7",
"resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-5.8.7.tgz",
"integrity": "sha512-tVqtowjbYmiRq+qcqXK731L9eWoL9H8xTRhuTgaDGKdch1zlt4I2UwInUe1w2N9N/u3/jHsFbLcl1Un3uOwpQg==",
"dependencies": {
"@babel/runtime": "^7.17.2",
"@emotion/cache": "^11.7.1",
"@emotion/cache": "^11.9.3",
"csstype": "^3.1.0",
"prop-types": "^15.8.1"
},
"engines": {
@ -3201,16 +3363,16 @@
}
},
"node_modules/@mui/system": {
"version": "5.8.6",
"resolved": "https://registry.npmjs.org/@mui/system/-/system-5.8.6.tgz",
"integrity": "sha512-+a+rD58XltKQHDrrjcuCta2cUBqdnLDUDwnphSLCMFigRl8/uk+R+fdQRlMNRXAOgnMb8ioWIgfjxri5pmTH4A==",
"version": "5.8.7",
"resolved": "https://registry.npmjs.org/@mui/system/-/system-5.8.7.tgz",
"integrity": "sha512-yFoFbfO42FWeSUDrFPixYjpqySQMqVMOSbSlAxiKnwFpvXGGn/bkfQTboCRNO31fvES29FJLQd4mwwMHd5mXng==",
"dependencies": {
"@babel/runtime": "^7.17.2",
"@mui/private-theming": "^5.8.6",
"@mui/styled-engine": "^5.8.0",
"@mui/styled-engine": "^5.8.7",
"@mui/types": "^7.1.4",
"@mui/utils": "^5.8.6",
"clsx": "^1.1.1",
"clsx": "^1.2.0",
"csstype": "^3.1.0",
"prop-types": "^15.8.1"
},
@ -3274,6 +3436,53 @@
"react": "^17.0.0 || ^18.0.0"
}
},
"node_modules/@mui/x-date-pickers": {
"version": "5.0.0-alpha.1",
"resolved": "https://registry.npmjs.org/@mui/x-date-pickers/-/x-date-pickers-5.0.0-alpha.1.tgz",
"integrity": "sha512-dLPkRiIn2Gr0momblxiOnIwrxn4SijVix+8e08mwAGWhiWcmWep1O9XTRDpZsjB0kjHYCf+kZjlRX4dxnj2acg==",
"dependencies": {
"@date-io/date-fns": "^2.11.0",
"@date-io/dayjs": "^2.11.0",
"@date-io/luxon": "^2.11.1",
"@date-io/moment": "^2.11.0",
"@mui/utils": "^5.6.0",
"clsx": "^1.1.1",
"prop-types": "^15.7.2",
"react-transition-group": "^4.4.2",
"rifm": "^0.12.1"
},
"engines": {
"node": ">=12.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/mui"
},
"peerDependencies": {
"@mui/material": "^5.2.3",
"@mui/system": "^5.2.3",
"date-fns": "^2.25.0",
"dayjs": "^1.10.7",
"luxon": "^1.28.0 || ^2.0.0",
"moment": "^2.29.1",
"react": "^17.0.2 || ^18.0.0",
"react-dom": "^17.0.2 || ^18.0.0"
},
"peerDependenciesMeta": {
"date-fns": {
"optional": true
},
"dayjs": {
"optional": true
},
"luxon": {
"optional": true
},
"moment": {
"optional": true
}
}
},
"node_modules/@nodelib/fs.scandir": {
"version": "2.1.5",
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
@ -5822,9 +6031,9 @@
}
},
"node_modules/clsx": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/clsx/-/clsx-1.1.1.tgz",
"integrity": "sha512-6/bPho624p3S2pMyvP5kKBPXnI3ufHLObBFCfgx+LkeR5lg2XYy2hqZqUf45ypD8COn2bhgGJSUE+l5dhNBieA==",
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/clsx/-/clsx-1.2.1.tgz",
"integrity": "sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg==",
"engines": {
"node": ">=6"
}
@ -16721,6 +16930,14 @@
"node": ">=0.10.0"
}
},
"node_modules/rifm": {
"version": "0.12.1",
"resolved": "https://registry.npmjs.org/rifm/-/rifm-0.12.1.tgz",
"integrity": "sha512-OGA1Bitg/dSJtI/c4dh90svzaUPt228kzFsUkJbtA2c964IqEAwWXeL9ZJi86xWv3j5SMqRvGULl7bA6cK0Bvg==",
"peerDependencies": {
"react": ">=16.8"
}
},
"node_modules/rimraf": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz",
@ -20487,6 +20704,43 @@
"postcss-value-parser": "^4.2.0"
}
},
"@date-io/core": {
"version": "2.14.0",
"resolved": "https://registry.npmjs.org/@date-io/core/-/core-2.14.0.tgz",
"integrity": "sha512-qFN64hiFjmlDHJhu+9xMkdfDG2jLsggNxKXglnekUpXSq8faiqZgtHm2lsHCUuaPDTV6wuXHcCl8J1GQ5wLmPw=="
},
"@date-io/date-fns": {
"version": "2.14.0",
"resolved": "https://registry.npmjs.org/@date-io/date-fns/-/date-fns-2.14.0.tgz",
"integrity": "sha512-4fJctdVyOd5cKIKGaWUM+s3MUXMuzkZaHuTY15PH70kU1YTMrCoauA7hgQVx9qj0ZEbGrH9VSPYJYnYro7nKiA==",
"requires": {
"@date-io/core": "^2.14.0"
}
},
"@date-io/dayjs": {
"version": "2.14.0",
"resolved": "https://registry.npmjs.org/@date-io/dayjs/-/dayjs-2.14.0.tgz",
"integrity": "sha512-4fRvNWaOh7AjvOyJ4h6FYMS7VHLQnIEeAV5ahv6sKYWx+1g1UwYup8h7+gPuoF+sW2hTScxi7PVaba2Jk/U8Og==",
"requires": {
"@date-io/core": "^2.14.0"
}
},
"@date-io/luxon": {
"version": "2.14.0",
"resolved": "https://registry.npmjs.org/@date-io/luxon/-/luxon-2.14.0.tgz",
"integrity": "sha512-KmpBKkQFJ/YwZgVd0T3h+br/O0uL9ZdE7mn903VPAG2ZZncEmaUfUdYKFT7v7GyIKJ4KzCp379CRthEbxevEVg==",
"requires": {
"@date-io/core": "^2.14.0"
}
},
"@date-io/moment": {
"version": "2.14.0",
"resolved": "https://registry.npmjs.org/@date-io/moment/-/moment-2.14.0.tgz",
"integrity": "sha512-VsoLXs94GsZ49ecWuvFbsa081zEv2xxG7d+izJsqGa2L8RPZLlwk27ANh87+SNnOUpp+qy2AoCAf0mx4XXhioA==",
"requires": {
"@date-io/core": "^2.14.0"
}
},
"@emotion/babel-plugin": {
"version": "11.7.2",
"resolved": "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.7.2.tgz",
@ -21287,6 +21541,40 @@
"@babel/runtime": "^7.17.2"
}
},
"@mui/lab": {
"version": "5.0.0-alpha.89",
"resolved": "https://registry.npmjs.org/@mui/lab/-/lab-5.0.0-alpha.89.tgz",
"integrity": "sha512-u5bMi/V+Utwouo9awVzGasj/LudlRqPFyMo2L5/y60uFo0EaG17bt1jh/U7smQCdjd+7tvJ39HNMkEmIoGr7BQ==",
"requires": {
"@babel/runtime": "^7.17.2",
"@mui/base": "5.0.0-alpha.88",
"@mui/system": "^5.8.7",
"@mui/utils": "^5.8.6",
"@mui/x-date-pickers": "5.0.0-alpha.1",
"clsx": "^1.2.0",
"prop-types": "^15.8.1",
"react-is": "^17.0.2",
"react-transition-group": "^4.4.2",
"rifm": "^0.12.1"
},
"dependencies": {
"@mui/base": {
"version": "5.0.0-alpha.88",
"resolved": "https://registry.npmjs.org/@mui/base/-/base-5.0.0-alpha.88.tgz",
"integrity": "sha512-uL7ej2F/3GUnZewsDQSHUVHoSBT3AQcTIdfdy6QeCHy7X26mtbcIvTRcjl2PzbbNQplppavSTibPiQG/giJ+ng==",
"requires": {
"@babel/runtime": "^7.17.2",
"@emotion/is-prop-valid": "^1.1.3",
"@mui/types": "^7.1.4",
"@mui/utils": "^5.8.6",
"@popperjs/core": "^2.11.5",
"clsx": "^1.2.0",
"prop-types": "^15.8.1",
"react-is": "^17.0.2"
}
}
}
},
"@mui/material": {
"version": "5.8.6",
"resolved": "https://registry.npmjs.org/@mui/material/-/material-5.8.6.tgz",
@ -21316,12 +21604,13 @@
}
},
"@mui/styled-engine": {
"version": "5.8.0",
"resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-5.8.0.tgz",
"integrity": "sha512-Q3spibB8/EgeMYHc+/o3RRTnAYkSl7ROCLhXJ830W8HZ2/iDiyYp16UcxKPurkXvLhUaILyofPVrP3Su2uKsAw==",
"version": "5.8.7",
"resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-5.8.7.tgz",
"integrity": "sha512-tVqtowjbYmiRq+qcqXK731L9eWoL9H8xTRhuTgaDGKdch1zlt4I2UwInUe1w2N9N/u3/jHsFbLcl1Un3uOwpQg==",
"requires": {
"@babel/runtime": "^7.17.2",
"@emotion/cache": "^11.7.1",
"@emotion/cache": "^11.9.3",
"csstype": "^3.1.0",
"prop-types": "^15.8.1"
}
},
@ -21350,16 +21639,16 @@
}
},
"@mui/system": {
"version": "5.8.6",
"resolved": "https://registry.npmjs.org/@mui/system/-/system-5.8.6.tgz",
"integrity": "sha512-+a+rD58XltKQHDrrjcuCta2cUBqdnLDUDwnphSLCMFigRl8/uk+R+fdQRlMNRXAOgnMb8ioWIgfjxri5pmTH4A==",
"version": "5.8.7",
"resolved": "https://registry.npmjs.org/@mui/system/-/system-5.8.7.tgz",
"integrity": "sha512-yFoFbfO42FWeSUDrFPixYjpqySQMqVMOSbSlAxiKnwFpvXGGn/bkfQTboCRNO31fvES29FJLQd4mwwMHd5mXng==",
"requires": {
"@babel/runtime": "^7.17.2",
"@mui/private-theming": "^5.8.6",
"@mui/styled-engine": "^5.8.0",
"@mui/styled-engine": "^5.8.7",
"@mui/types": "^7.1.4",
"@mui/utils": "^5.8.6",
"clsx": "^1.1.1",
"clsx": "^1.2.0",
"csstype": "^3.1.0",
"prop-types": "^15.8.1"
}
@ -21382,6 +21671,22 @@
"react-is": "^17.0.2"
}
},
"@mui/x-date-pickers": {
"version": "5.0.0-alpha.1",
"resolved": "https://registry.npmjs.org/@mui/x-date-pickers/-/x-date-pickers-5.0.0-alpha.1.tgz",
"integrity": "sha512-dLPkRiIn2Gr0momblxiOnIwrxn4SijVix+8e08mwAGWhiWcmWep1O9XTRDpZsjB0kjHYCf+kZjlRX4dxnj2acg==",
"requires": {
"@date-io/date-fns": "^2.11.0",
"@date-io/dayjs": "^2.11.0",
"@date-io/luxon": "^2.11.1",
"@date-io/moment": "^2.11.0",
"@mui/utils": "^5.6.0",
"clsx": "^1.1.1",
"prop-types": "^15.7.2",
"react-transition-group": "^4.4.2",
"rifm": "^0.12.1"
}
},
"@nodelib/fs.scandir": {
"version": "2.1.5",
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
@ -23283,9 +23588,9 @@
}
},
"clsx": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/clsx/-/clsx-1.1.1.tgz",
"integrity": "sha512-6/bPho624p3S2pMyvP5kKBPXnI3ufHLObBFCfgx+LkeR5lg2XYy2hqZqUf45ypD8COn2bhgGJSUE+l5dhNBieA=="
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/clsx/-/clsx-1.2.1.tgz",
"integrity": "sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg=="
},
"co": {
"version": "4.6.0",
@ -30957,6 +31262,12 @@
"resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz",
"integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw=="
},
"rifm": {
"version": "0.12.1",
"resolved": "https://registry.npmjs.org/rifm/-/rifm-0.12.1.tgz",
"integrity": "sha512-OGA1Bitg/dSJtI/c4dh90svzaUPt228kzFsUkJbtA2c964IqEAwWXeL9ZJi86xWv3j5SMqRvGULl7bA6cK0Bvg==",
"requires": {}
},
"rimraf": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz",

@ -7,6 +7,7 @@
"@emotion/styled": "^11.9.3",
"@mui-treasury/styles": "^1.13.1",
"@mui/icons-material": "^5.2.5",
"@mui/lab": "^5.0.0-alpha.89",
"@mui/material": "^5.8.6",
"@mui/styles": "^5.8.6",
"@testing-library/jest-dom": "^5.16.1",

@ -1,7 +1,7 @@
import React, { useState } from 'react'
import HomePage from './pages/HomePage.jsx'
import LoginPage from './pages/LoginPage.jsx'
import ImageDetails from './components/ImageDetails.jsx'
import RepoDetails from './components/RepoDetails.jsx'
import makeStyles from '@mui/styles/makeStyles';
@ -9,6 +9,7 @@ import './App.css';
import { BrowserRouter as Router, Routes, Route, Navigate } from "react-router-dom";
import { AuthWrapper } from 'utilities/AuthWrapper.jsx';
import RepoPage from 'pages/RepoPage.jsx';
const useStyles = makeStyles((theme) => ({
@ -36,7 +37,7 @@ function App() {
<Route element={<AuthWrapper isLoggedIn={isLoggedIn} redirect="/login" />}>
<Route path="/" element={<Navigate to="/home" />} />
<Route path="/home" element={<HomePage keywords={searchKeywords} updateKeywords={setSearchKeywords} data={data} updateData={setData} />} />
<Route path="/image/:name" element={<ImageDetails username={username} password={password} />} />
<Route path="/image/:name" element={<RepoPage />} />
</Route>
<Route element={<AuthWrapper isLoggedIn={!isLoggedIn} redirect="/"/>}>
<Route path="/login" element={<LoginPage username={username} password={password} updateUsername={setUsername} updatePassword={setPassword} isAuthEnabled={isAuthEnabled} setIsAuthEnabled={setIsAuthEnabled} isLoggedIn={isLoggedIn} setIsLoggedIn={setIsLoggedIn} />} />

@ -62,4 +62,9 @@ const api = {
};
export default api;
const endpoints = {
imageList: '/v2/_zot/ext/search?query={ImageListWithLatestTag () { Name Latest LastUpdated Description Licenses Vendor Size Labels}}',
detailedRepoInfo: (name) => `/v2/_zot/ext/search?query={ExpandedRepoInfo(repo:"${name}"){Manifests {Digest Tag Layers {Size Digest}}}}`
}
export {api, endpoints};

@ -11,8 +11,7 @@ import { Container, FormControl, Grid, InputLabel, Select, Stack } from '@mui/ma
import makeStyles from '@mui/styles/makeStyles';
// utility
import api from '../api.js';
import {URL} from '../constants';
import {api, endpoints} from '../api';
import {host} from '../constants';
import {isEmpty} from 'lodash';
import FilterCard from './FilterCard.jsx';
@ -44,7 +43,7 @@ function Explore ({ keywords, data, updateData }) {
const classes = useStyles();
useEffect(() => {
api.get(`${host}${URL.imageList}`)
api.get(`${host}${endpoints.imageList}`)
.then(response => {
if (response.data && response.data.data) {
let imageList = response.data.data.ImageListWithLatestTag;

@ -14,14 +14,16 @@ const useStyles = makeStyles((theme) => {
console.log("theme", theme)
return {
exploreHeader: {
backgroundColor: "#fafafa",
backgroundColor: "#FFFFFF",
minHeight: 50,
paddingLeft: 5,
display: "flex",
alignItems: "center",
justifyContent: "center"
},
explore: {
color: 'gray'
color: '#00000099',
letterSpacing: "0.15px"
}
}
});
@ -33,9 +35,9 @@ function ExploreHeader() {
return (
<div className={classes.exploreHeader}>
<Breadcrumbs separator={<NavigateNextIcon fontSize="small" />} aria-label="breadcrumb">
<Link to="/"><Typography className={classes.explore}>Explore Packages</Typography></Link>
{ path.includes('/image/') && <Typography>{path.replace('/image/', '')}</Typography> }
<Breadcrumbs separator="/" aria-label="breadcrumb">
<Link to="/"><Typography variant="body1" className={classes.explore}>Home</Typography></Link>
{ path.includes('/image/') && <Typography className={classes.explore} variant="body1">{path.replace('/image/', '')}</Typography> }
</Breadcrumbs>
</div>
);

@ -168,7 +168,6 @@ function Header({ updateKeywords }) {
</Popper>
</div>
</Toolbar>
{ path !== '/login' && path !== '/' && path !== '/home' && <ExploreHeader /> }
</AppBar>
);
}

@ -1,111 +0,0 @@
// react global
import { useParams, useLocation } from 'react-router-dom'
import React, { useEffect, useState } from 'react';
// utility
import axios from 'axios';
// components
import Header from './Header.jsx'
import RepoCard from './RepoCard.jsx'
import Tags from './Tags.jsx'
import {Container, Box, Grid} from '@mui/material';
import { URL } from '../constants';
import makeStyles from '@mui/styles/makeStyles';
const useStyles = makeStyles((theme) => ({
pageWrapper: {
backgroundColor: "#f2f2f2a1",
},
container: {
paddingTop: 5,
paddingBottom: 5,
marginTop: 100,
backgroundColor: "#f2f2f2a1",
},
parentWrapper: {
height: '100vh',
},
gridWrapper: {
paddingTop: 10,
paddingBottom: 10,
backgroundColor: "#fff",
border: "1px #f2f2f2 dashed",
},
}));
function ImageDetails (props) {
const {host, username, password} = props;
const [imageDetailData, setImageDetailData] = useState({});
const [isLoading, setIsLoading] = useState(false);
// get data from <Link here
const location = useLocation();
const myData = location && location.state && location.state.data;
// get url param from <Route here (i.e. image name)
const {name} = useParams();
const classes = useStyles();
useEffect(() => {
const {name, version} = myData;
const token = btoa(username + ':' + password);
const cfg = {
headers: {
'Authorization': `Basic ${token}`,
}
};
axios.get(`${host}${URL.imageList}`, cfg)
.then(response => {
if (response.data && response.data.data) {
let imageList = response.data.data.ExpandedRepoInfo;
let imageData = {
name: name,
tags: imageList.Manifests
}
setImageDetailData(imageData);
setIsLoading(false);
}
})
.catch(() => {
setImageDetailData({});
});
}, [])
return (
<div className={classes.pageWrapper}>
<Header updateKeywords={props.updateKeywords}></Header>
<Container maxWidth="md" className={classes.container}>
<div className={classes.parentWrapper}>
<Grid container className={classes.gridWrapper}>
<Grid item md={1} ></Grid>
<Grid item md={10}>
<Box>
<RepoCard className={classes.tile}
name={myData.name}
version={myData.latestVersion}
description={myData.description}
tags={myData.tags}
vendor={myData.vendor}
size={myData.size}
licenses={myData.licenses}
key={myData}
size="lg"
shown={true}
/>
</Box>
<Tags data={imageDetailData} />
</Grid>
<Grid item md={1} ></Grid>
</Grid>
</div>
</Container>
</div>
);
}
export default ImageDetails;

@ -70,8 +70,8 @@ function RepoCard(props) {
const navigate = useNavigate();
const {name, vendor, description, lastUpdated, downloads, rating} = props;
const goToDetails = () => {
navigate(`/image/${name}`);
const goToDetails = (repo) => {
navigate(`/image/${name}`, {state: {lastDate: (lastUpdated? DateTime.fromISO(lastUpdated) : DateTime.now().minus({days:1})).toRelative({unit:'days'})}});
}
const verifiedCheck = () => {

@ -0,0 +1,296 @@
// react global
import { useLocation, useParams } from 'react-router-dom'
import React, { useEffect, useState } from 'react';
// utility
import {api, endpoints} from '../api';
import mockData from '../utilities/mockData';
// components
import Tags from './Tags.jsx'
import {Box, Card, CardContent, CardMedia, Chip, FormControl, Grid, IconButton, InputAdornment, OutlinedInput, Stack, Tab, Typography} from '@mui/material';
import makeStyles from '@mui/styles/makeStyles';
import { host } from '../constants';
import CheckCircleOutlineOutlinedIcon from '@mui/icons-material/CheckCircleOutlineOutlined';
import ContentCopyIcon from '@mui/icons-material/ContentCopy';
// placeholder images
import repocube1 from '../assets/repocube-1.png';
import repocube2 from '../assets/repocube-2.png';
import repocube3 from '../assets/repocube-3.png';
import repocube4 from '../assets/repocube-4.png';
import { TabContext, TabList, TabPanel } from '@mui/lab';
import RepoDetailsMetadata from './RepoDetailsMetadata';
// @ts-ignore
const useStyles = makeStyles((theme) => ({
pageWrapper: {
backgroundColor: "#FFFFFF",
},
container: {
paddingTop: 5,
paddingBottom: 5,
marginTop: 100,
backgroundColor: "#FFFFFF",
},
parentWrapper: {
height: '100vh',
},
gridWrapper: {
paddingTop: 10,
paddingBottom: 10,
backgroundColor: "#FFFFFF",
border: "1px #f2f2f2 dashed",
},
avatar: {
height:"48px",
width:"48px"
},
cardBtn: {
height: "100%",
width: "100%"
},
media: {
borderRadius: '50px',
},
tabs: {
marginTop: "5%",
border: 1,
borderColor: 'divider',
padding:"8px",
boxShadow: "0px 5px 10px rgba(131, 131, 131, 0.08)",
background:"#EDE7F6",
borderRadius:"32px",
height: "100%"
},
selectedTab: {
background:"#A53692",
borderRadius:"24px"
},
tabPanel: {
height:"100%"
},
metadata: {
padding:"24px"
},
card: {
marginBottom: 2,
display:"flex",
flexDirection:"row",
alignItems:"start",
background:"#FFFFFF",
boxShadow:"0px 5px 10px rgba(131, 131, 131, 0.08)",
borderRadius:"24px",
flex:"none",
alignSelf:"stretch",
flexGrow:0,
order:0,
width:"100%"
},
}));
// temporary utility to get image
const randomIntFromInterval = (min, max) => {
return Math.floor(Math.random() * (max - min + 1) + min)
};
const randomImage = () => {
const imageArray = [repocube1,repocube2,repocube3,repocube4];
return imageArray[randomIntFromInterval(0,3)];
};
function RepoDetails (props) {
const [repoDetailData, setRepoDetailData] = useState({});
// @ts-ignore
const [isLoading, setIsLoading] = useState(false);
const [selectedTab, setSelectedTab] = useState("Readme");
// get url param from <Route here (i.e. image name)
const {name} = useParams();
const {state} = useLocation();
// @ts-ignore
const {lastDate} = state;
const classes = useStyles();
const {description, readmeTitle, dependencies, dependents} = props;
useEffect(() => {
api.get(`${host}${endpoints.detailedRepoInfo(name)}`)
.then(response => {
if (response.data && response.data.data) {
let imageList = response.data.data.ExpandedRepoInfo;
let imageData = {
name: name,
tags: imageList.Manifests
}
setRepoDetailData(imageData);
setIsLoading(false);
}
})
.catch((e) => {
console.error(e);
setRepoDetailData({});
});
}, [])
const getLatestManifest = () => {
// @ts-ignore
const manifests = repoDetailData.tags || [{}];
return manifests[0];
}
const getLatestLayer = () => {
const layers = getLatestManifest().Layers || [{}];
return layers[0];
}
const verifiedCheck = () => {
return (<CheckCircleOutlineOutlinedIcon sx={{color:"#388E3C!important"}}/>);
}
const platformChips = () => {
// if platforms not received, mock data
const platforms = props.platforms || ["Windows","PowerPC64LE","IBM Z","Linux"];
return platforms.map((platform, index) => (
<Chip key={index} label={platform} sx={{backgroundColor:"#EDE7F6", color: "#311B92"}}/>
));
}
// @ts-ignore
const handleTabChange = (event, newValue) => {
setSelectedTab(newValue);
};
const renderReadme = () => {
return (
<Card className={classes.card}>
<CardContent>
<Typography variant="h4" align="left">{readmeTitle || 'Quickstart'}</Typography>
<Typography variant="body1">{description || mockData.loremIpsum}</Typography>
</CardContent>
</Card>
);
};
const renderDependencies = () => {
return (<Card className={classes.card}>
<CardContent>
<Typography variant="h4" align="left">Dependecies ({dependencies || '---'})</Typography>
</CardContent>
</Card>);
};
const renderDependents = () => {
return (<Card className={classes.card}>
<CardContent>
<Typography variant="h4" align="left">Dependents ({dependents || '---'})</Typography>
</CardContent>
</Card>);
};
const renderVulnerabilities = () => {
return (<Card className={classes.card}>
<CardContent>
<Typography variant="h4" align="left">Vulnerabilities</Typography>
</CardContent>
</Card>);
};
return (
<div className={classes.pageWrapper}>
<div className={classes.parentWrapper}>
<Card variant="outlined">
<CardContent>
<Grid container>
<Grid item xs={7}>
<Stack alignItems="center" direction="row" spacing={2}>
<CardMedia classes={{
root: classes.media,
img: classes.avatar,
}}
component="img"
image={randomImage()}
alt="icon"
/>
<Typography variant="h5" component="div">
{name}
</Typography>
<Chip label="Verified license" sx={{backgroundColor:"#E8F5E9", color:"#388E3C"}} variant="filled" onDelete={() => {return}} deleteIcon={verifiedCheck()}/>
</Stack>
<Typography pt={1} sx={{ fontSize: 12 }} gutterBottom align="left">
{description || 'The complete solution for node.js command-line programs'}
</Typography>
<Stack alignItems="center" direction="row" spacing={2} pt={1}>
{platformChips()}
</Stack>
</Grid>
<Grid item xs={5} pt={2}>
<Typography variant="body1">Copy and pull to pull this image</Typography>
<FormControl sx={{ m: 1, width: '25ch' }} variant="outlined">
<OutlinedInput
value={`Pull ${name}`}
endAdornment={
<InputAdornment position="end">
<IconButton aria-label='copy' edge="end" onClick={() => navigator.clipboard.writeText(`Pull ${name}`)}>
<ContentCopyIcon/>
</IconButton>
</InputAdornment>
}
aria-describedby="outlined-weight-helper-text"
inputProps={{
'aria-label': 'weight',
}}
/>
</FormControl>
</Grid>
</Grid>
<TabContext value={selectedTab}>
<Box className={classes.tabs}>
<TabList onChange={handleTabChange} TabIndicatorProps={{ className: classes.selectedTab }} >
<Tab value="Readme" label="Read me"/>
<Tab value="Tags" label="Tags"/>
<Tab value="Dependencies" label={`${dependencies || 0} dependencies`}/>
<Tab value="Dependents" label={`${dependents || 0} dependents`}/>
<Tab value="Vulnerabilities" label="Vulnerabilities"/>
<Tab value="6" label="Tab 6"/>
<Tab value="7" label="Tab 7"/>
<Tab value="8" label="Tab 8"/>
</TabList>
<Grid container>
<Grid item xs={8}>
<TabPanel value="Readme" className={classes.tabPanel}>
{renderReadme()}
</TabPanel>
<TabPanel value="Tags" className={classes.tabPanel}>
<Tags data={repoDetailData} />
</TabPanel>
<TabPanel value="Dependencies" className={classes.tabPanel}>
{renderDependencies()}
</TabPanel>
<TabPanel value="Dependents" className={classes.tabPanel}>
{renderDependents()}
</TabPanel>
<TabPanel value="Vulnerabilities" className={classes.tabPanel}>
{renderVulnerabilities()}
</TabPanel>
</Grid>
<Grid item xs={4} className={classes.metadata}>
<RepoDetailsMetadata
name={name}
lastUpdated={lastDate}
size={getLatestLayer()?.size}
latestTag={getLatestManifest()?.Tag}
/>
</Grid>
</Grid>
</Box>
</TabContext>
</CardContent>
</Card>
</div>
</div>
);
}
export default RepoDetails;

@ -0,0 +1,133 @@
import { Card, CardContent, Grid, Typography } from '@mui/material';
import makeStyles from '@mui/styles/makeStyles';
import React from 'react';
import transform from '../utilities/transform';
const useStyles = makeStyles(() => ({
card: {
marginBottom: 2,
display:"flex",
flexDirection:"row",
alignItems:"start",
background:"#FFFFFF",
boxShadow:"0px 5px 10px rgba(131, 131, 131, 0.08)",
borderRadius:"24px",
flex:"none",
alignSelf:"stretch",
flexGrow:0,
order:0,
width:"100%"
},
metadataHeader: {
color: "rgba(0, 0, 0, 0.6)"
},
metadataBody: {
color: "rgba(0, 0, 0, 0.87)",
fontFamily: 'Roboto',
fontStyle: "normal",
fontWeight: 400,
fontSize: "16px",
lineHeight: "150%",
align:"left"
}
}));
function RepoDetailsMetadata (props) {
const classes = useStyles();
const {name, repoURL, weeklyDownloads, lastUpdated, size, filesNr, latestTag, issues, prs} = props;
return (
<Grid container spacing={1}>
<Grid container item xs={12}>
<Card variant="outlined" className={classes.card}>
<CardContent>
<Typography variant="body2" align="left" className={classes.metadataHeader}>Repository</Typography>
<Typography variant="body1" className={classes.metadataBody}>{repoURL || `----`}</Typography>
</CardContent>
</Card>
</Grid>
<Grid container item xs={12}>
<Card variant="outlined" className={classes.card}>
<CardContent>
<Typography variant="body2" align="left" className={classes.metadataHeader}>Weekly downloads</Typography>
<Typography variant="body1" align="left" className={classes.metadataBody}>{weeklyDownloads || `----`}</Typography>
</CardContent>
</Card>
</Grid>
<Grid container item xs={12} spacing={2}>
<Grid item xs={6}>
<Card variant="outlined" className={classes.card}>
<CardContent>
<Typography variant="body2" align="left" className={classes.metadataHeader}>Last publish</Typography>
<Typography variant="body1" className={classes.metadataBody}>{lastUpdated || `35 days ago`}</Typography>
</CardContent>
</Card>
</Grid>
<Grid item xs={6}>
<Card variant="outlined" className={classes.card}>
<CardContent>
<Typography variant="body2" align="left" className={classes.metadataHeader}>Image size</Typography>
<Typography variant="body1" className={classes.metadataBody}>{transform.formatBytes(size) || `----`}</Typography>
</CardContent>
</Card>
</Grid>
</Grid>
<Grid container item xs={12} spacing={2}>
<Grid item xs={6}>
<Card variant="outlined" className={classes.card}>
<CardContent>
<Typography variant="body2" align="left" className={classes.metadataHeader}>Last publish</Typography>
<Typography variant="body1" className={classes.metadataBody}>{lastUpdated || `----`}</Typography>
</CardContent>
</Card>
</Grid>
<Grid item xs={6}>
<Card variant="outlined" className={classes.card}>
<CardContent>
<Typography variant="body2" align="left" className={classes.metadataHeader}>Image size</Typography>
<Typography variant="body1" className={classes.metadataBody}>{size || `----`}</Typography>
</CardContent>
</Card>
</Grid>
</Grid>
<Grid container item xs={12} spacing={2}>
<Grid item xs={6}>
<Card variant="outlined" className={classes.card}>
<CardContent>
<Typography variant="body2" align="left" className={classes.metadataHeader}>Files</Typography>
<Typography variant="body1" align="left" className={classes.metadataBody}>{filesNr || `----`}</Typography>
</CardContent>
</Card>
</Grid>
<Grid item xs={6}>
<Card variant="outlined" className={classes.card}>
<CardContent>
<Typography variant="body2" align="left" className={classes.metadataHeader}>Latest tag</Typography>
<Typography variant="body1" align="left" className={classes.metadataBody}>{latestTag || `----`}</Typography>
</CardContent>
</Card>
</Grid>
</Grid>
<Grid container item xs={12} spacing={2}>
<Grid item xs={6}>
<Card variant="outlined" className={classes.card}>
<CardContent>
<Typography variant="body2" align="left" className={classes.metadataHeader}>Issues</Typography>
<Typography variant="body1" align="left" className={classes.metadataBody}>{issues || `----`}</Typography>
</CardContent>
</Card>
</Grid>
<Grid item xs={6}>
<Card variant="outlined" className={classes.card}>
<CardContent>
<Typography variant="body2" align="left" className={classes.metadataHeader}>Pull requests</Typography>
<Typography variant="body1" align="left" className={classes.metadataBody}>{prs || `----`}</Typography>
</CardContent>
</Card>
</Grid>
</Grid>
</Grid>
)
}
export default RepoDetailsMetadata;

@ -3,8 +3,7 @@ import React, { useEffect, useState } from 'react';
import { useNavigate } from "react-router-dom";
import { host } from '../constants';
// utility
import api from '../api';
import { URL } from '../constants';
import {api, endpoints} from '../api';
// components
import Button from '@mui/material/Button';
@ -80,7 +79,7 @@ export default function SignIn({ username, updateUsername, password, updatePassw
}
};
}
api.get(`${host}${URL.imageList}`,cfg)
api.get(`${host}${endpoints.imageList}`,cfg)
.then(response => {
if (response.data && response.data.data) {
if(isAuthEnabled) {

@ -5,102 +5,88 @@ import PropTypes from 'prop-types';
// components
import Box from '@mui/material/Box';
import Collapse from '@mui/material/Collapse';
import IconButton from '@mui/material/IconButton';
import Table from '@mui/material/Table';
import TableBody from '@mui/material/TableBody';
import TableCell from '@mui/material/TableCell';
import TableContainer from '@mui/material/TableContainer';
import TableCell, {tableCellClasses} from '@mui/material/TableCell';
import TableHead from '@mui/material/TableHead';
import TableRow from '@mui/material/TableRow';
import Typography from '@mui/material/Typography';
import Paper from '@mui/material/Paper';
import KeyboardArrowDownIcon from '@mui/icons-material/KeyboardArrowDown';
import KeyboardArrowUpIcon from '@mui/icons-material/KeyboardArrowUp';
import ContentCopyIcon from '@mui/icons-material/ContentCopy';
import transform from 'utilities/transform';
import { Card, CardContent, Divider } from '@mui/material';
import { makeStyles } from '@mui/styles';
const useStyles = makeStyles(() => ({
card: {
marginBottom: 2,
display:"flex",
flexDirection:"row",
alignItems:"center",
background:"#FFFFFF",
boxShadow:"0px 5px 10px rgba(131, 131, 131, 0.08)",
borderRadius:"30px",
flex:"none",
alignSelf:"stretch",
flexGrow:0,
order:0,
width:"100%"
},
content: {
textAlign: "left",
color: "#606060",
padding: "2% 3% 2% 3%",
width:"100%"
}
}));
// takes raw # of bytes and decimal value to be returned;
// returns bytes with nearest human-readable unit
function formatBytes(bytes) {
if (isNaN(bytes) || bytes === 0) {
return 0;
}
const DATA_UNITS = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
const k = 1000;
const unitIdx = Math.floor(Math.log10(bytes) / 3); // log10(1000) = 3
let value = bytes / Math.pow(k, unitIdx);
// minimum 2 significant digits
value = value < 10 ? value.toPrecision(2) : Math.round(value);
return value + ' ' + DATA_UNITS[unitIdx];
}
function Row(props) {
function TagCard(props) {
const {data, row} = props;
const tags = data && data.tags;
const [open, setOpen] = React.useState(false);
const classes = useStyles();
return (
<React.Fragment>
<TableRow sx={{ '& > *': { borderBottom: 'unset' } }}>
<TableCell component="th" scope="row" style={{color: "#696969"}}>
<IconButton
aria-label="expand row"
size="medium"
onClick={() => setOpen(!open)}
>
{open ? <KeyboardArrowUpIcon /> : <KeyboardArrowDownIcon />}
</IconButton>
{row.Tag}
</TableCell>
<TableCell />
</TableRow>
<TableRow>
<TableCell style={{ paddingBottom: 0, paddingTop: 0 }} colSpan={6}>
<Collapse in={open} timeout="auto" unmountOnExit>
<Box sx={{ margin: 1 }}>
<Typography variant="h7" gutterBottom component="div">
{
// Layers
}
</Typography>
<Table size="small" aria-label="purchases">
<TableHead>
<TableRow>
<TableCell style={{color: "#696969"}}>Size</TableCell>
<TableCell style={{color: "#696969"}}>Digest</TableCell>
<Card className={classes.card} raised>
<CardContent className={classes.content}>
<Typography variant="body1" align="left" sx={{color:"#828282"}}>{row.Tag}</Typography>
<Typography variant="caption">Last pushed {row.lastUpdated || '----'} by {row.vendor || '----'}</Typography>
<Typography sx={{color:"#7C4DFF", cursor:'pointer'}} onClick={() => setOpen(!open)}>{!open? 'See layers' : 'Hide layers'}</Typography>
<Collapse in={open} timeout="auto" unmountOnExit>
<Box>
<Typography variant="h6" gutterBottom component="div">
{
// Layers
}
</Typography>
<Table size="small" padding="none" sx={{[`& .${tableCellClasses.root}`]: {borderBottom: "none"}}}>
<TableHead>
<TableRow>
<TableCell style={{color: "#696969"}}><Typography variant="body1">Digest</Typography></TableCell>
<TableCell style={{color: "#696969"}}><Typography variant="body1">OS/ARCH</Typography></TableCell>
<TableCell style={{color: "#696969"}}><Typography variant="body1">Size</Typography></TableCell>
</TableRow>
</TableHead>
<TableBody>
{row.Layers.map((layer) => (
<TableRow key={layer.Digest} onClick={() => {navigator.clipboard.writeText(layer.Digest)}}>
<TableCell style={{color: "#696969"}}><Typography variant="body1">{layer.Digest?.substr(0,12)}</Typography></TableCell>
<TableCell style={{color: "#696969"}}><Typography variant="body1">-----------</Typography></TableCell>
<TableCell component="th" scope="row" style={{color: "#696969"}}>
<Typography variant="body1">{transform.formatBytes(layer.Size)}</Typography>
</TableCell>
</TableRow>
</TableHead>
<TableBody>
{row.Layers.map((layer) => (
<TableRow key={layer.Digest}>
<TableCell component="th" scope="row" style={{color: "#696969"}}>
{formatBytes(layer.Size)}
</TableCell>
<TableCell style={{color: "#696969"}}>{layer.Digest}</TableCell>
<TableCell style={{color: "#696969"}}>
<ContentCopyIcon sx={{height: 16, width: 16}} onClick={() => {navigator.clipboard.writeText(layer.Digest)}} />
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</Box>
</Collapse>
</TableCell>
</TableRow>
</React.Fragment>
))}
</TableBody>
</Table>
</Box>
</Collapse>
</CardContent>
</Card>
);
}
Row.propTypes = {
TagCard.propTypes = {
row: PropTypes.shape({
calories: PropTypes.number.isRequired,
carbs: PropTypes.number.isRequired,
fat: PropTypes.number.isRequired,
Layers: PropTypes.arrayOf(
PropTypes.shape({
amount: PropTypes.number.isRequired,
@ -109,8 +95,6 @@ Row.propTypes = {
}),
).isRequired,
Tag: PropTypes.string.isRequired,
price: PropTypes.number.isRequired,
protein: PropTypes.number.isRequired,
}).isRequired,
};
@ -118,7 +102,7 @@ Row.propTypes = {
const renderTags = (tags) => {
const cmp = tags && tags.map((tag, index) => {
return (
<Row key={tag.Tag} row={tag} />
<TagCard key={tag.Tag} row={tag} />
);
});
return cmp;
@ -126,22 +110,17 @@ const renderTags = (tags) => {
export default function CollapsibleTable(props) {
const classes = useStyles();
const {data} = props;
const {tags} = data;
return (
<TableContainer component={Paper}>
<Table aria-label="collapsible table">
<TableHead>
<TableRow>
<TableCell> <Typography variant="h7" gutterBottom component="div" style={{color: "#696969"}}>Tags</Typography></TableCell>
<TableCell />
</TableRow>
</TableHead>
<TableBody>
{renderTags(tags)}
</TableBody>
</Table>
</TableContainer>
<Card className={classes.card}>
<CardContent className={classes.content}>
<Typography variant="h4" gutterBottom component="div" align="left" style={{color: "rgba(0, 0, 0, 0.87)"}}>Tags</Typography>
<Divider variant="fullWidth" sx={{margin:"5% 0% 5% 0%", background:"rgba(0, 0, 0, 0.38)", height:"1px", width:"100%"}}/>
{renderTags(tags)}
</CardContent>
</Card>
);
}

@ -1,8 +1,3 @@
const URL = {
imageList: '/v2/_zot/ext/search?query={ImageListWithLatestTag () { Name Latest LastUpdated Description Licenses Vendor Size Labels}}',
};
const host = 'http://localhost:5000'
export {URL, host};
export {host};

51
src/pages/RepoPage.jsx Normal file

@ -0,0 +1,51 @@
// react global
import React from 'react';
// components
import makeStyles from '@mui/styles/makeStyles';
import { Container, Grid, Stack } from '@mui/material';
import Header from 'components/Header';
import RepoDetails from 'components/RepoDetails';
import ExploreHeader from 'components/ExploreHeader';
const useStyles = makeStyles((theme) => ({
pageWrapper: {
backgroundColor: "#f2f2f2a1",
},
container: {
paddingTop: 5,
paddingBottom: 5,
backgroundColor: "#f2f2f2a1",
},
parentWrapper: {
height: '100vh',
},
gridWrapper: {
paddingTop: 10,
paddingBottom: 10,
backgroundColor: "#fff",
width:"100%",
border: "1px #f2f2f2 dashed",
},
}));
function RepoPage(props) {
const classes = useStyles();
return (
<Stack direction="column" className={classes.pageWrapper}>
<Header updateKeywords={props.updateKeywords}></Header>
<Container className={classes.container} >
<ExploreHeader/>
<Grid container className={classes.gridWrapper}>
<Grid item xs={12}>
<RepoDetails />
</Grid>
</Grid>
</Container>
</Stack>
);
}
export default RepoPage;

@ -0,0 +1,5 @@
const mockData = {
loremIpsum: "Lorem ipsum dolor sit amet, consectetur adipiscing elit. At quam nulla euismod mus sed. Gravida ornare tortor diam nullam donec eget. Sed et cursus tristique leo arcu eget sed. Sit neque morbi praesent tellus duis lectus orci.\n Laoreet sodales aenean libero pharetra tellus. Nisl blandit quis lorem platea. Mauris id neque nec blandit ipsum aliquet venenatis egestas sed. Tempus felis sed aliquam proin aliquet. Elementum egestas sagittis nibh orci, varius interdum pretium risus. Nullam eu id tempus faucibus nullam purus nibh mi.\n Tincidunt pretium vehicula metus a dui est proin. Ullamcorper vitae pulvinar diam habitant a, tellus pellentesque consectetur et. Vel, pellentesque tellus ultrices non molestie. Eu, est dignissim sit vivamus. At orci, urna rhoncus sed ultrices. Dui elit dui vestibulum ipsum sed morbi pellentesque sed lacus."
}
export default mockData;

@ -0,0 +1,23 @@
const transform = {
// takes raw # of bytes and decimal value to be returned;
// returns bytes with nearest human-readable unit
formatBytes : (bytes) => {
if (isNaN(bytes) || bytes === 0) {
return 0;
}
const DATA_UNITS = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
const k = 1000;
const unitIdx = Math.floor(Math.log10(bytes) / 3); // log10(1000) = 3
let value = bytes / Math.pow(k, unitIdx);
// minimum 2 significant digits
// @ts-ignore
value = value < 10 ? value.toPrecision(2) : Math.round(value);
return value + ' ' + DATA_UNITS[unitIdx];
}
}
export default transform;