implemented navbar

This commit is contained in:
CodeServer 2022-04-11 08:50:03 +01:00
parent f5cddc4bcd
commit 9a49b30cd1
12 changed files with 168 additions and 10 deletions

View File

@ -1,4 +1,5 @@
import { useLocation, Outlet, Navigate } from "react-router-dom"; import { useLocation, Outlet, Navigate } from "react-router-dom";
import NavBar from "./components/navbar/NavBar";
import useAuth from "./hooks/useAuth"; import useAuth from "./hooks/useAuth";
const ProtectedRoute = () => { const ProtectedRoute = () => {
@ -7,6 +8,7 @@ const ProtectedRoute = () => {
return ((auth.loginNeeded !== undefined && !auth.loginNeeded) || auth?.isLoggedIn) ? ( return ((auth.loginNeeded !== undefined && !auth.loginNeeded) || auth?.isLoggedIn) ? (
<> <>
<NavBar />
<Outlet /> <Outlet />
</> </>
) : ( ) : (

View File

@ -6,12 +6,14 @@ import axios from "../../api/axios";
import useAuth from "../../hooks/useAuth"; import useAuth from "../../hooks/useAuth";
import Tag from "../../interfaces/Tag"; import Tag from "../../interfaces/Tag";
import Repository from "../../interfaces/Repositoriy"; import Repository from "../../interfaces/Repositoriy";
import Layer from "../../interfaces/Layer";
const Home: React.FC = () => { const Home: React.FC = () => {
const auth = useAuth(); const auth = useAuth();
const [repositories, setRepositories] = useState<Repository[]>([]); const [repositories, setRepositories] = useState<Repository[]>([]);
auth['repositories'] = repositories;
const getTagInfo = async(newRepositories: Repository[]) => { const getTagInfo = async(newRepositories: Repository[]) => {
let promises: Promise<void>[] = []; let promises: Promise<void>[] = [];
newRepositories.forEach((repository: Repository) => { newRepositories.forEach((repository: Repository) => {
@ -28,7 +30,16 @@ const Home: React.FC = () => {
} }
} }
).then((response) => { ).then((response) => {
tag.schemaVersion = response?.data['schemaVersion'];
tag.mediaType = response?.data['mediaType'];
tag.digest = response?.headers['docker-content-digest']; 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( return axios.get(
"/" + repository.name + "/blobs/" + response?.data?.config['digest'], "/" + repository.name + "/blobs/" + response?.data?.config['digest'],
{ {
@ -71,6 +82,11 @@ const Home: React.FC = () => {
architecture: "", architecture: "",
os: "", os: "",
created: undefined, created: undefined,
schemaVersion: undefined,
mediaType: "",
config: undefined,
layers: undefined,
size: undefined,
digest: "" digest: ""
} }
tags.push(tag); tags.push(tag);

View File

@ -1,8 +1,8 @@
import React from "react"; 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 Tag from "../../interfaces/Tag";
import Repository from "../../interfaces/Repositoriy"; import Repository from "../../interfaces/Repositoriy";
import { printTimePassed } from "../../utils"; import { printSize, printTimePassed } from "../../utils";
interface Props { interface Props {
repository: Repository; repository: Repository;
@ -14,10 +14,18 @@ const RepositoryItem: React.FC<Props> = (props: Props) => {
<ListItemButton <ListItemButton
component="a" component="a"
> >
<Box sx={{ display: "flex", flexDirection: "column" }}> {/* <Box sx={{ display: "flex", flexDirection: "column" }}>
<Typography color="black" variant="h5">{props.repository.name}</Typography> </Box> */}
<Typography color="grey" variant="h6">{"Last updated: " + props.repository.tags.filter((tag: Tag) => (tag.label === "latest")).map((tag: Tag) => (tag.created ? printTimePassed(tag.created) : "")).join(" ")}</Typography> <Grid container>
</Box> <Grid item sm={9}>
<ListItemText primary={props.repository.name} secondary={"Last updated: " + props.repository.tags.filter((tag: Tag) => (tag.label === "latest")).map((tag: Tag) => (tag.created ? printTimePassed(tag.created) : "")).join(" ")} />
{/* <Typography color="black" variant="h5">{props.repository.name}</Typography>
<Typography color="grey" variant="h6">{"Last updated: " + props.repository.tags.filter((tag: Tag) => (tag.label === "latest")).map((tag: Tag) => (tag.created ? printTimePassed(tag.created) : "")).join(" ")}</Typography> */}
</Grid>
<Grid item sm={3}>
<ListItemText style={{display:'flex', justifyContent:'flex-end'}} primary={props.repository.tags.filter((tag: Tag) => (tag.label === "latest")).map((tag: Tag) => (tag.size ? printSize(tag.size) : "")).join(" ")} />
</Grid>
</Grid>
</ListItemButton> </ListItemButton>
); );
} }

View File

@ -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 (
<AppBar className="navbar" position="static">
<Container maxWidth={false} sx={{ m: 0, width: '100%' }}>
<Toolbar disableGutters>
<Typography
variant="h6"
noWrap
component="div"
sx={{ mr: 2, display: 'flex' }}
>
Docker Registry
</Typography>
<Box sx={{ flexGrow: 1, display: 'flex' }}>
<Typography onClick={handleClick} className="centerText" > Your registry instance is at {process.env.REGISTRY_URL ? process.env.REGISTRY_URL : ""} </Typography>
</Box>
<Snackbar open={snackbarOpen} autoHideDuration={6000} onClose={handleClose}>
<Alert onClose={handleClose} severity="success" sx={{ width: '100%' }}>
Registry address copied!
</Alert>
</Snackbar>
<Box sx={{ flexGrow: 0 }}>
<Tooltip title="Open settings">
<IconButton onClick={(event) => {setAnchorElUser(event.currentTarget)}} sx={{ p: 0 }}>
<Avatar alt={auth['username']} src="/pic.png" />
</IconButton>
</Tooltip>
<Menu
sx={{ mt: '45px' }}
id="menu-appbar"
anchorEl={anchorElUser}
anchorOrigin={{
vertical: 'top',
horizontal: 'right',
}}
keepMounted
transformOrigin={{
vertical: 'top',
horizontal: 'right',
}}
open={Boolean(anchorElUser)}
onClose={handleCloseUserMenu}
>
<MenuItem onClick={handleCloseUserMenu}>
<Typography textAlign="center">Account</Typography>
</MenuItem>
</Menu>
</Box>
</Toolbar>
</Container>
</AppBar>
);
};
export default NavBar;

View File

@ -1,21 +1,24 @@
import { createContext, useState } from "react"; import { createContext, useState } from "react";
import Repository from "../interfaces/Repositoriy";
interface loginInfo { interface loginInfo {
loginNeeded: boolean; loginNeeded: boolean;
isLoggedIn: boolean; isLoggedIn: boolean;
username: string; username: string;
password: string; password: string;
repositories: Repository[];
} }
const AuthContext = createContext<loginInfo>({ const AuthContext = createContext<loginInfo>({
loginNeeded: true, loginNeeded: true,
isLoggedIn: false, isLoggedIn: false,
username: "", username: "",
password: "" password: "",
repositories: []
}); });
export const AuthProvider = ({ children }: { children: React.ReactNode }) => { export const AuthProvider = ({ children }: { children: React.ReactNode }) => {
const [auth] = useState<loginInfo>({ loginNeeded: true, isLoggedIn: false, username: "", password: "" }); const [auth] = useState<loginInfo>({ loginNeeded: true, isLoggedIn: false, username: "", password: "", repositories: [] });
return <AuthContext.Provider value={auth}>{children}</AuthContext.Provider>; return <AuthContext.Provider value={auth}>{children}</AuthContext.Provider>;
}; };

View File

@ -1,11 +1,13 @@
import { useContext } from "react"; import { useContext } from "react";
import AuthContext from "../context/AuthProvider"; import AuthContext from "../context/AuthProvider";
import Repository from "../interfaces/Repositoriy";
interface loginInfo { interface loginInfo {
loginNeeded: boolean; loginNeeded: boolean;
isLoggedIn: boolean; isLoggedIn: boolean;
username: string; username: string;
password: string; password: string;
repositories: Repository[];
} }
const useAuth = () => { const useAuth = () => {

7
src/interfaces/Layer.tsx Normal file
View File

@ -0,0 +1,7 @@
interface Layer {
mediaType: string;
size: number;
digest: string;
};
export default Layer;

View File

@ -1,8 +1,21 @@
import Layer from "./Layer"
interface Config {
mediaType: string;
size: number;
digest: string;
};
interface Tag { interface Tag {
label: string; label: string;
architecture: string; architecture: string;
os: string; os: string;
created: Date | undefined; created: Date | undefined;
schemaVersion: number | undefined;
mediaType: string;
config: Config | undefined;
layers: Layer[] | undefined;
size: number | undefined;
digest: string; digest: string;
} }

View File

@ -0,0 +1,5 @@
.navbar
margin: 0
.centerText
text-align: center
width: 90%

View File

@ -6,6 +6,15 @@ a {
text-decoration: none; text-decoration: none;
} }
.navbar {
margin: 0;
}
.navbar .centerText {
text-align: center;
width: 90%;
}
.sectionHome .repositoryList { .sectionHome .repositoryList {
max-width: 70%; max-width: 70%;
margin: auto; margin: auto;

View File

@ -1,6 +1,6 @@
{ {
"version": 3, "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": [ "sources": [
"style.sass", "style.sass",
"_variables.sass", "_variables.sass",

View File

@ -15,6 +15,10 @@ const checkValidURL = async (url: string) => {
return true; return true;
} }
const printTwoDecimalPlaces = (num: number): string => {
return (Math.round(num * 100) / 100).toFixed(2);
}
const printTimePassed = (date: Date): string => { const printTimePassed = (date: Date): string => {
const currentTime = Date.now(); const currentTime = Date.now();
const secondsPassed = (currentTime - date.getTime()) / 1000; const secondsPassed = (currentTime - date.getTime()) / 1000;
@ -37,4 +41,12 @@ const printTimePassed = (date: Date): string => {
} }
}; };
export { checkValidURL , printTimePassed }; const printSize = (sizeInByte: number): string => {
if (sizeInByte < 1073741824) {
return printTwoDecimalPlaces(sizeInByte / 1048576) + " MiB";
} else {
return printTwoDecimalPlaces(sizeInByte / 1073741824) + " GiB";
}
};
export { checkValidURL, printTwoDecimalPlaces, printTimePassed, printSize };