implemented navbar
This commit is contained in:
parent
f5cddc4bcd
commit
9a49b30cd1
@ -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) ? (
|
||||
<>
|
||||
<NavBar />
|
||||
<Outlet />
|
||||
</>
|
||||
) : (
|
||||
|
||||
@ -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<Repository[]>([]);
|
||||
|
||||
auth['repositories'] = repositories;
|
||||
const getTagInfo = async(newRepositories: Repository[]) => {
|
||||
let promises: Promise<void>[] = [];
|
||||
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);
|
||||
|
||||
@ -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) => {
|
||||
<ListItemButton
|
||||
component="a"
|
||||
>
|
||||
<Box sx={{ display: "flex", flexDirection: "column" }}>
|
||||
<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>
|
||||
</Box>
|
||||
{/* <Box sx={{ display: "flex", flexDirection: "column" }}>
|
||||
</Box> */}
|
||||
<Grid container>
|
||||
<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>
|
||||
);
|
||||
}
|
||||
|
||||
81
src/components/navbar/NavBar.tsx
Normal file
81
src/components/navbar/NavBar.tsx
Normal 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;
|
||||
@ -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<loginInfo>({
|
||||
loginNeeded: true,
|
||||
isLoggedIn: false,
|
||||
username: "",
|
||||
password: ""
|
||||
password: "",
|
||||
repositories: []
|
||||
});
|
||||
|
||||
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>;
|
||||
};
|
||||
|
||||
@ -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 = () => {
|
||||
|
||||
7
src/interfaces/Layer.tsx
Normal file
7
src/interfaces/Layer.tsx
Normal file
@ -0,0 +1,7 @@
|
||||
interface Layer {
|
||||
mediaType: string;
|
||||
size: number;
|
||||
digest: string;
|
||||
};
|
||||
|
||||
export default Layer;
|
||||
@ -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;
|
||||
}
|
||||
|
||||
|
||||
@ -0,0 +1,5 @@
|
||||
.navbar
|
||||
margin: 0
|
||||
.centerText
|
||||
text-align: center
|
||||
width: 90%
|
||||
@ -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;
|
||||
|
||||
@ -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",
|
||||
|
||||
@ -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 };
|
||||
const printSize = (sizeInByte: number): string => {
|
||||
if (sizeInByte < 1073741824) {
|
||||
return printTwoDecimalPlaces(sizeInByte / 1048576) + " MiB";
|
||||
} else {
|
||||
return printTwoDecimalPlaces(sizeInByte / 1073741824) + " GiB";
|
||||
}
|
||||
};
|
||||
|
||||
export { checkValidURL, printTwoDecimalPlaces, printTimePassed, printSize };
|
||||
Loading…
Reference in New Issue
Block a user