diff --git a/src/ProtectedRoute.tsx b/src/ProtectedRoute.tsx index 79ea547..1427265 100644 --- a/src/ProtectedRoute.tsx +++ b/src/ProtectedRoute.tsx @@ -1,4 +1,5 @@ import { useLocation, Outlet, Navigate } from "react-router-dom"; +import NavBar from "./components/navbar/NavBar"; import useAuth from "./hooks/useAuth"; const ProtectedRoute = () => { @@ -7,6 +8,7 @@ const ProtectedRoute = () => { return ((auth.loginNeeded !== undefined && !auth.loginNeeded) || auth?.isLoggedIn) ? ( <> + ) : ( diff --git a/src/components/home/Home.tsx b/src/components/home/Home.tsx index fee1aca..71c2081 100644 --- a/src/components/home/Home.tsx +++ b/src/components/home/Home.tsx @@ -6,12 +6,14 @@ import axios from "../../api/axios"; import useAuth from "../../hooks/useAuth"; import Tag from "../../interfaces/Tag"; import Repository from "../../interfaces/Repositoriy"; +import Layer from "../../interfaces/Layer"; const Home: React.FC = () => { const auth = useAuth(); const [repositories, setRepositories] = useState([]); + auth['repositories'] = repositories; const getTagInfo = async(newRepositories: Repository[]) => { let promises: Promise[] = []; newRepositories.forEach((repository: Repository) => { @@ -28,7 +30,16 @@ const Home: React.FC = () => { } } ).then((response) => { + tag.schemaVersion = response?.data['schemaVersion']; + tag.mediaType = response?.data['mediaType']; tag.digest = response?.headers['docker-content-digest']; + tag.config = response?.data?.config; + tag.layers = response?.data?.layers; + let size = 0; + response?.data?.layers.forEach((layer: Layer) => { + size += layer.size; + }); + tag.size = size; return axios.get( "/" + repository.name + "/blobs/" + response?.data?.config['digest'], { @@ -71,6 +82,11 @@ const Home: React.FC = () => { architecture: "", os: "", created: undefined, + schemaVersion: undefined, + mediaType: "", + config: undefined, + layers: undefined, + size: undefined, digest: "" } tags.push(tag); diff --git a/src/components/home/RepositoryEntry.tsx b/src/components/home/RepositoryEntry.tsx index 13e9bc5..74c7448 100644 --- a/src/components/home/RepositoryEntry.tsx +++ b/src/components/home/RepositoryEntry.tsx @@ -1,8 +1,8 @@ import React from "react"; -import { ListItemButton, Box, Typography, tableFooterClasses } from "@mui/material"; +import { ListItemButton, Box, Typography, Grid, ListItemText } from "@mui/material"; import Tag from "../../interfaces/Tag"; import Repository from "../../interfaces/Repositoriy"; -import { printTimePassed } from "../../utils"; +import { printSize, printTimePassed } from "../../utils"; interface Props { repository: Repository; @@ -14,10 +14,18 @@ const RepositoryItem: React.FC = (props: Props) => { - - {props.repository.name} - {"Last updated: " + props.repository.tags.filter((tag: Tag) => (tag.label === "latest")).map((tag: Tag) => (tag.created ? printTimePassed(tag.created) : "")).join(" ")} - + {/* + */} + + + (tag.label === "latest")).map((tag: Tag) => (tag.created ? printTimePassed(tag.created) : "")).join(" ")} /> + {/* {props.repository.name} + {"Last updated: " + props.repository.tags.filter((tag: Tag) => (tag.label === "latest")).map((tag: Tag) => (tag.created ? printTimePassed(tag.created) : "")).join(" ")} */} + + + (tag.label === "latest")).map((tag: Tag) => (tag.size ? printSize(tag.size) : "")).join(" ")} /> + + ); } diff --git a/src/components/navbar/NavBar.tsx b/src/components/navbar/NavBar.tsx new file mode 100644 index 0000000..f544f6f --- /dev/null +++ b/src/components/navbar/NavBar.tsx @@ -0,0 +1,81 @@ +import React from "react"; +import { AppBar, Container, Toolbar, Typography, Box, IconButton, Menu, MenuItem , Tooltip, Avatar, Snackbar, Alert} from "@mui/material"; +import useAuth from "../../hooks/useAuth"; + +const NavBar: React.FC = () => { + const auth = useAuth(); + const [anchorElUser, setAnchorElUser] = React.useState<(EventTarget & HTMLButtonElement) | null>(null); + const [snackbarOpen, setSnackbarOpen] = React.useState(false); + + const handleCloseUserMenu = () => { + setAnchorElUser(null); + }; + + const handleClick = () => { + const url = process.env.REGISTRY_URL ? process.env.REGISTRY_URL : ""; + const urlSplit = url.split('/'); + navigator.clipboard.writeText(urlSplit[urlSplit.length - 1]); + setSnackbarOpen(true); + }; + + const handleClose = () => { + setSnackbarOpen(false); + }; + + return ( + + + + + Docker Registry + + + + Your registry instance is at {process.env.REGISTRY_URL ? process.env.REGISTRY_URL : ""} + + + + + Registry address copied! + + + + + + {setAnchorElUser(event.currentTarget)}} sx={{ p: 0 }}> + + + + + + Account + + + + + + + ); +}; + +export default NavBar; \ No newline at end of file diff --git a/src/context/AuthProvider.tsx b/src/context/AuthProvider.tsx index 48a149e..2c99a2c 100644 --- a/src/context/AuthProvider.tsx +++ b/src/context/AuthProvider.tsx @@ -1,21 +1,24 @@ import { createContext, useState } from "react"; +import Repository from "../interfaces/Repositoriy"; interface loginInfo { loginNeeded: boolean; isLoggedIn: boolean; username: string; password: string; + repositories: Repository[]; } const AuthContext = createContext({ loginNeeded: true, isLoggedIn: false, username: "", - password: "" + password: "", + repositories: [] }); export const AuthProvider = ({ children }: { children: React.ReactNode }) => { - const [auth] = useState({ loginNeeded: true, isLoggedIn: false, username: "", password: "" }); + const [auth] = useState({ loginNeeded: true, isLoggedIn: false, username: "", password: "", repositories: [] }); return {children}; }; diff --git a/src/hooks/useAuth.tsx b/src/hooks/useAuth.tsx index 2602ce8..32e8aee 100644 --- a/src/hooks/useAuth.tsx +++ b/src/hooks/useAuth.tsx @@ -1,11 +1,13 @@ import { useContext } from "react"; import AuthContext from "../context/AuthProvider"; +import Repository from "../interfaces/Repositoriy"; interface loginInfo { loginNeeded: boolean; isLoggedIn: boolean; username: string; password: string; + repositories: Repository[]; } const useAuth = () => { diff --git a/src/interfaces/Layer.tsx b/src/interfaces/Layer.tsx new file mode 100644 index 0000000..e0b36cf --- /dev/null +++ b/src/interfaces/Layer.tsx @@ -0,0 +1,7 @@ +interface Layer { + mediaType: string; + size: number; + digest: string; +}; + +export default Layer; \ No newline at end of file diff --git a/src/interfaces/Tag.tsx b/src/interfaces/Tag.tsx index cfe6c29..6ced8cd 100644 --- a/src/interfaces/Tag.tsx +++ b/src/interfaces/Tag.tsx @@ -1,8 +1,21 @@ +import Layer from "./Layer" + +interface Config { + mediaType: string; + size: number; + digest: string; +}; + interface Tag { label: string; architecture: string; os: string; created: Date | undefined; + schemaVersion: number | undefined; + mediaType: string; + config: Config | undefined; + layers: Layer[] | undefined; + size: number | undefined; digest: string; } diff --git a/src/style/layouts/_header.sass b/src/style/layouts/_header.sass index e69de29..1dcd5e2 100644 --- a/src/style/layouts/_header.sass +++ b/src/style/layouts/_header.sass @@ -0,0 +1,5 @@ +.navbar + margin: 0 + .centerText + text-align: center + width: 90% \ No newline at end of file diff --git a/src/style/style.css b/src/style/style.css index 4bc1fd9..be2d42e 100644 --- a/src/style/style.css +++ b/src/style/style.css @@ -6,6 +6,15 @@ a { text-decoration: none; } +.navbar { + margin: 0; +} + +.navbar .centerText { + text-align: center; + width: 90%; +} + .sectionHome .repositoryList { max-width: 70%; margin: auto; diff --git a/src/style/style.css.map b/src/style/style.css.map index a8443ca..8d4b21a 100644 --- a/src/style/style.css.map +++ b/src/style/style.css.map @@ -1,6 +1,6 @@ { "version": 3, - "mappings": "AG8CA,AAAA,WAAW,CAAC;EACR,KAAK,EAAE,IAAI;CAAG;;AAElB,AAAA,CAAC,CAAC;EACE,eAAe,EAAE,IAAI;CAAG;;AKlD5B,AACI,YADQ,CACR,eAAe,CAAC;EACZ,SAAS,EAAE,GAAG;EACd,MAAM,EAAE,IAAI;CAAG;;ACHvB,AACI,MADE,CACF,eAAe,CAAC;EACZ,WAAW,EAAE,MAAM;EACnB,MAAM,EAAE,IAAI;EACZ,UAAU,EAAE,GAAG;EACf,SAAS,EAAE,cAAc;CAeS;;AApB1C,AAMQ,MANF,CACF,eAAe,GAKT,CAAC,CAAC;EACA,KAAK,EAAE,GAAG;EACV,MAAM,EAAE,MAAM;CAAG;;AAR7B,AASQ,MATF,CACF,eAAe,CAQX,YAAY,CAAC;EACT,OAAO,EAAE,YAAY;EACrB,UAAU,EAAE,MAAM;EAClB,MAAM,EAAE,IAAI;EACZ,SAAS,EAAE,IAAI;CAAG;;AAb9B,AAcQ,MAdF,CACF,eAAe,CAaX,UAAU,CAAC;EACP,MAAM,EAAE,IAAI;EACZ,KAAK,EAAE,GAAG;CACpB;;AAjBF,AAkBQ,MAlBF,CACF,eAAe,CAiBX,eAAe,CAAC;EACZ,MAAM,EAAE,IAAI;EACZ,gBAAgB,EAAE,OAAO;CAAG;;ACpBxC,AAAA,QAAQ,CAAC;EACL,SAAS,EAAE,GAAG;EACd,MAAM,EAAE,IAAI;EACZ,UAAU,EAAE,GAAG;CAEU;;AAL7B,AAII,QAJI,CAIJ,eAAe,CAAC;EACZ,UAAU,EAAE,IAAI;CAAG", + "mappings": "AG8CA,AAAA,WAAW,CAAC;EACR,KAAK,EAAE,IAAI;CAAG;;AAElB,AAAA,CAAC,CAAC;EACE,eAAe,EAAE,IAAI;CAAG;;AElD5B,AAAA,OAAO,CAAC;EACJ,MAAM,EAAE,CAAC;CAGU;;AAJvB,AAEI,OAFG,CAEH,WAAW,CAAC;EACR,UAAU,EAAE,MAAM;EAClB,KAAK,EAAE,GAAG;CAAG;;AGJrB,AACI,YADQ,CACR,eAAe,CAAC;EACZ,SAAS,EAAE,GAAG;EACd,MAAM,EAAE,IAAI;CAAG;;ACHvB,AACI,MADE,CACF,eAAe,CAAC;EACZ,WAAW,EAAE,MAAM;EACnB,MAAM,EAAE,IAAI;EACZ,UAAU,EAAE,GAAG;EACf,SAAS,EAAE,cAAc;CAeS;;AApB1C,AAMQ,MANF,CACF,eAAe,GAKT,CAAC,CAAC;EACA,KAAK,EAAE,GAAG;EACV,MAAM,EAAE,MAAM;CAAG;;AAR7B,AASQ,MATF,CACF,eAAe,CAQX,YAAY,CAAC;EACT,OAAO,EAAE,YAAY;EACrB,UAAU,EAAE,MAAM;EAClB,MAAM,EAAE,IAAI;EACZ,SAAS,EAAE,IAAI;CAAG;;AAb9B,AAcQ,MAdF,CACF,eAAe,CAaX,UAAU,CAAC;EACP,MAAM,EAAE,IAAI;EACZ,KAAK,EAAE,GAAG;CACpB;;AAjBF,AAkBQ,MAlBF,CACF,eAAe,CAiBX,eAAe,CAAC;EACZ,MAAM,EAAE,IAAI;EACZ,gBAAgB,EAAE,OAAO;CAAG;;ACpBxC,AAAA,QAAQ,CAAC;EACL,SAAS,EAAE,GAAG;EACd,MAAM,EAAE,IAAI;EACZ,UAAU,EAAE,GAAG;CAEU;;AAL7B,AAII,QAJI,CAIJ,eAAe,CAAC;EACZ,UAAU,EAAE,IAAI;CAAG", "sources": [ "style.sass", "_variables.sass", diff --git a/src/utils.tsx b/src/utils.tsx index f7a14cf..1c661c6 100644 --- a/src/utils.tsx +++ b/src/utils.tsx @@ -15,6 +15,10 @@ const checkValidURL = async (url: string) => { return true; } +const printTwoDecimalPlaces = (num: number): string => { + return (Math.round(num * 100) / 100).toFixed(2); +} + const printTimePassed = (date: Date): string => { const currentTime = Date.now(); const secondsPassed = (currentTime - date.getTime()) / 1000; @@ -37,4 +41,12 @@ const printTimePassed = (date: Date): string => { } }; -export { checkValidURL , printTimePassed }; \ No newline at end of file +const printSize = (sizeInByte: number): string => { + if (sizeInByte < 1073741824) { + return printTwoDecimalPlaces(sizeInByte / 1048576) + " MiB"; + } else { + return printTwoDecimalPlaces(sizeInByte / 1073741824) + " GiB"; + } +}; + +export { checkValidURL, printTwoDecimalPlaces, printTimePassed, printSize }; \ No newline at end of file