Rewrite page in Svelte 5 #49

Merged
Neshura merged 94 commits from rewrite/svelte into main 2024-01-06 14:12:09 +00:00
31 changed files with 1543 additions and 6133 deletions
Showing only changes of commit 04a1cd1580 - Show all commits

View file

@ -1 +0,0 @@
export const fetcher = (url:string) => fetch(url).then((res) => res.json());

View file

@ -1,19 +0,0 @@
import { Footer, MobileFooter } from "../components/styles/generic"
const PageFooter = () => {
return (
<Footer>
Built using Next.js
</Footer>
);
}
export const NavMenuFooter = () => {
return (
<MobileFooter>
Built using Next.js
</MobileFooter>
);
}
export default PageFooter;

View file

@ -1,35 +0,0 @@
import PageFooter from './footer';
import PageNavbar from './navbar';
import Script from 'next/script';
import { Page, Main } from './styles/generic';
import useWindowSize from './windowsize';
const Layout = ({ children }: { children: React.ReactNode }) => {
const isMobile = useWindowSize();
let ret: JSX.Element;
if(isMobile) {
ret = (
<Page mobile={isMobile}>
<PageNavbar mobile={isMobile}/>
<Main>
{children}
</Main>
</Page>
);
}
else {
ret = (
<Page>
<PageNavbar mobile={isMobile}/>
<Main>
{children}
</Main>
<PageFooter />
</Page>
);
}
return ret;
}
export default Layout;

View file

@ -1,74 +0,0 @@
import { usePathname } from 'next/navigation'
import { NavBarMobile, NavIndicator, NavIndicators , NavSideMenu, NavSideMenuButton, NavSideMenuPanel, NavLinkMobile, NavWrapMobile, NavWrapMobileGhost, NavSideMenuGhost } from './styles/navbar/mobile';
import { NavBar, NavLink, NavWrap } from './styles/navbar/desktop';
import { StyleSelector, StyleSelectorPlaceholder } from './themeselector';
import Links from '../public/data/navbar.json';
import { useState } from 'react';
import { NavMenuFooter } from './footer';
const PageNavbar = ({ mobile }: { mobile: number }) => {
const path = usePathname();
const [sideBarActive, setSideBarActive] = useState(false);
function handleSidebar(event: any) {
if (!event.currentTarget.contains(event.relatedTarget)) {
setSideBarActive(false);
}
}
let navbar: JSX.Element;
if (mobile) {
navbar = (
<>
<NavSideMenu tabIndex={-1} onBlur={(event) => handleSidebar(event)} active={+sideBarActive}>
<NavSideMenuButton onClick={() => setSideBarActive(sideBarActive => !sideBarActive)} active={+sideBarActive}>Menu</NavSideMenuButton>
<NavSideMenuPanel active={+sideBarActive}>
<NavBarMobile>
{Links.links.map((item) => (
<NavLinkMobile active={path === item.href ? +true : +false} key={item.name} href={item.href}>
{item.name}
</NavLinkMobile>
))}
<NavLinkMobile key="Mastodon_Verify" rel="me" href="https://mastodon.neshweb.net/@neshura">
Mastodon
</NavLinkMobile>
</NavBarMobile>
<NavSideMenuGhost />
<StyleSelector mobile={mobile}/>
<NavSideMenuGhost num={2}/>
<NavMenuFooter />
</NavSideMenuPanel>
</NavSideMenu>
<NavWrapMobile>
<NavIndicators>
{Links.links.map((item) => (
<NavIndicator active={path === item.href ? +true : +false} key={item.name} href={item.href} aria-label={item.name}/>
))}
</NavIndicators>
</NavWrapMobile>
<NavWrapMobileGhost />
</>
);
}
else {
navbar = (
<NavWrap>
<StyleSelector mobile={mobile}/>
<NavBar>
{Links.links.map((item) => (
<NavLink active={path === item.href ? +true : +false} key={item.name} href={item.href}>
{item.name}
</NavLink>
))}
<NavLink key="Mastodon_Verify" rel="me" href="https://mastodon.neshweb.net/@neshura">
Mastodon
</NavLink>
</NavBar>
<StyleSelectorPlaceholder />
</NavWrap>
);
}
return navbar;
}
export default PageNavbar;

View file

@ -1,325 +0,0 @@
import { Service } from '../../../interfaces/CardTypes';
import styled, { css, DefaultTheme } from 'styled-components';
import Link from 'next/link';
import Image from 'next/image';
import OpenInNewTabIcon from '../../../public/icons/open-new-window.svg'
// needed for Online Status checks
interface OnlinePropType {
status: string;
}
interface BorderHelperType {
border_left: boolean;
}
const Card = styled.div<OnlinePropType>`
position: relative;
display: flex;
flex-direction: column;
align-items: center;
width: 30rem;
max-width: 90%;
height: 12.5rem;
margin: 1rem;
// themeing
border-top: 0.25rem solid;
border-radius: 10px;
color: ${({ theme }) => theme.colors.primary};
border-color: ${props => {
let ret;
switch (props.status) {
case "Online":
ret = ({ theme }: { theme: DefaultTheme }) => theme.colors.online;
break;
case "Loading":
ret = ({ theme }: { theme: DefaultTheme }) => theme.colors.loading;
break;
case "Offline":
ret = ({ theme }: { theme: DefaultTheme }) => theme.colors.offline;
break;
default:
ret = ({ theme }: { theme: DefaultTheme }) => theme.colors.offline;
}
return ret;
}};
background-color: ${({ theme }) => theme.colors.backgroundAlt ? theme.colors.backgroundAlt : theme.colors.background};
transition-property: max-height, margin-bottom;
transition-duration: 0.2s, 0s;
transition-delay: 0.2s, 0.2s;
`
// custom objects for CardTitle
//#############################
const CardHeaderWrap = styled.div`
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: center;
width: 100%;
max-height: 3.5rem;
flex-grow: 0.8;
`;
const CardTitleText = styled.h2`
font-size: 1.2rem;
margin: 0.5rem 0;
`;
const CardTitleIcon = styled.div`
position: relative;
object-fit: contain;
margin-right: 0.4rem;
aspect-ratio: 1;
height: 1.5rem;
`;
const CardStatus = styled.p<OnlinePropType>`
display: flex;
flex-direction: row;
font-size: 0.9rem;
padding: 0.1rem 0.3rem;
margin: 0.5rem;
margin-right: 1.5rem;
color: ${props => {
let ret;
switch (props.status) {
case "Online":
ret = ({ theme }: { theme: DefaultTheme }) => theme.colors.online;
break;
case "Loading":
ret = ({ theme }: { theme: DefaultTheme }) => theme.colors.loading;
break;
case "Offline":
ret = ({ theme }: { theme: DefaultTheme }) => theme.colors.offline;
break;
default:
ret = ({ theme }: { theme: DefaultTheme }) => theme.colors.offline;
}
return ret;
}};
border-radius: 0.5rem;
border: 0;
border-bottom: 0.125rem solid;
background: transparent;
`
const CardTitle = styled.div`
display: flex;
flex-direction: row;
justify-content: center;
align-items: center;
margin: 0.5rem;
padding-left: 1rem;
`
const CardTitleLink = styled(Link)`
display: flex;
flex-direction: row;
justify-content: center;
align-items: center;
margin: 0.5rem;
padding-left: 1rem;
&:hover {
color: ${({ theme }) => theme.colors.secondary};
}
`
// content visible when reduced
const CardHeader = ({ content }: { content: Service }) => {
return (
<CardHeaderWrap>
{
content.href ?
<CardTitleLink href={content.href}>
{
content.icon ? (
<CardTitleIcon>
<Image alt="icon" src={content.icon} fill sizes={'1.5rem'}/>
</CardTitleIcon>
) : (<></>)
}
<CardTitleText>{content.name}</CardTitleText>
<OpenInNewTab>
<OpenInNewTabIcon width="16px" height="16px" />
</OpenInNewTab>
</CardTitleLink> :
<CardTitle>
{
content.icon ? (
<CardTitleIcon>
<Image alt="icon" src={content.icon} fill sizes={'1.5rem'}/>
</CardTitleIcon>
) : (<></>)
}
<CardTitleText>{content.name}</CardTitleText>
</CardTitle>
}
<CardStatus status={content.status}>{content.status}</CardStatus>
</CardHeaderWrap>
)
}
// custom objects for CardDescription
//###################################
// shared properties for all Description objects
const CardDescriptionCommon = css`
text-align: left;
font-size: 0.9rem;
margin: 0.3rem;
`
// content visible when expanded
const CardDescriptionWrap = styled.div`
${CardDescriptionCommon}
padding: 0 1rem;
overflow: hidden; /* Hide scrollbars */
scrollbar-width: thin;
width: 100%;
`
const CardDescriptionExtended = styled.p`
${CardDescriptionCommon}
margin: 0;
margin-bottom: 0.9rem;
width: 100%;
`
const CardDescriptionWarning = styled(CardDescriptionExtended)`
text-align: center;
color: ${({ theme }) => theme.colors.offline};
font-weight: bold;
`
const CardDescription = ({ content }: { content: Service }) => {
return (
<CardDescriptionWrap>
<CardDescriptionExtended>
{content.desc}
</CardDescriptionExtended>
<CardDescriptionWarning>
{content.warn}
</CardDescriptionWarning>
</CardDescriptionWrap>
)
}
// custom objects for CardFooter
//##############################
const CardFooterWrap = styled.div`
display: grid;
grid-template-columns: repeat(auto-fit, minmax(0, 1fr));
grid-auto-flow: row;
//flex-direction: row;
align-items: center;
justify-items: center;
width: 100%;
position: absolute;
bottom: 5%;
`
const CardLink = styled(Link)`
display: flex;
flex-direction: row;
justify-content: center;
align-items: center;
margin: 0.5rem;
margin-left: 1rem;
margin-right: 1rem;
padding: 0 0.5rem;
border: 0;
border-radius: 5px;
border-left: 2px solid;
border-right: 2px solid;
transition-property: overflow-x;
transition-duration: 0.2s;
&:hover {
background-color: ${({ theme }) => theme.colors.background};
color: ${({ theme }) => theme.colors.secondary};
}
transition-property: background-color;
transition-duration: 0.5s;
`
const OpenInNewTab = styled.div`
color: ${({ theme }) => theme.colors.primary};
object-fit: contain;
aspect-ratio: 1;
height: 1rem;
width: 1rem;
max-width: 0;
overflow: hidden;
visibility: hidden;
${CardLink}:hover & {
margin-left: 0.3rem;
max-width: 1rem;
transition-delay: 0s, 0s;
visibility: visible;
color: ${({ theme }) => theme.colors.secondary};
}
${CardTitleLink}:hover & {
margin-left: 0.3rem;
max-width: 1rem;
transition-delay: 0s, 0s;
visibility: visible;
color: ${({ theme }) => theme.colors.secondary};
}
transition-property: max-width, margin-left, visibility;
transition-duration: 0.5s, 0s, 0S;
transition-delay: 0s, 0.2s, 0.2s;
`
const CardFooter = ({ content, href }: { content: Service, href: string }) => {
return (
<CardFooterWrap>
{href ? (
<CardLink href={href}>
Open
<OpenInNewTab>
<OpenInNewTabIcon width="16px" height="16px" />
</OpenInNewTab>
</CardLink>
) : (<></>)}
{content.extLink ? (
<CardLink href={content.extLink}>
Official Site
<OpenInNewTab>
<OpenInNewTabIcon width="16px" height="16px" />
</OpenInNewTab>
</CardLink>
) : (<></>)}
</CardFooterWrap>
)
}
// exported Card Elements
//#######################
export const ServiceCardDesktop = ({ content }: { content: Service }) => {
return (
<Card status={content.status}>
<CardHeader content={content} />
<CardDescription content={content} />
<CardFooter content={content} href={content.href ? content.href : ""} />
</Card>
)
}

View file

@ -1,249 +0,0 @@
import { Service } from '../../../interfaces/CardTypes';
import styled, { css, DefaultTheme } from 'styled-components';
import Link from 'next/link';
import Image from 'next/image';
import OpenInNewTabIcon from '../../../public/icons/open-new-window.svg'
// needed for Online Status checks
interface OnlinePropType {
status: string;
}
const Card = styled.div`
position: relative;
display: flex;
flex-direction: column;
align-items: center;
width: 30rem;
max-width: 90%;
min-height: 10rem;
max-height: 12.5rem;
margin: 1rem;
// themeing
border-top: 0.25rem solid;
border-radius: 10px;
color: ${({ theme }) => theme.colors.primary};
border-color: ${({ theme }) => theme.colors.primary};
background-color: ${({ theme }) => theme.colors.background};
transition-property: max-height, margin-bottom;
transition-duration: 0.2s, 0s;
transition-delay: 0.2s, 0.2s;
`
// custom objects for CardTitle
//#############################
const CardTitleWrap = styled.div`
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: center;
width: 100%;
flex-grow: 0.8;
`;
const CardTitleText = styled.h2`
font-size: 1.2rem;
margin: 0.5rem 0;
`;
const CardTitleIcon = styled.div`
position: relative;
object-fit: contain;
margin-right: 0.4rem;
aspect-ratio: 1;
height: 1.5rem;
`;
const OpenInNewTab = styled.div`
color: ${({theme}) => theme.colors.primary };
position: relative;
object-fit: contain;
padding: 0.2rem;
margin-left: 0.5rem;
aspect-ratio: 1;
height: 1.5rem;
`
const CardStatus = styled.p<OnlinePropType>`
font-size: 0.9rem;
padding: 0.1rem;
margin: 0.5rem;
margin-right: 1.5rem;
border-radius: 5px;
border: 0;
border-bottom: 0.125rem solid;
background: transparent;
color: ${props => {
let ret;
switch (props.status) {
case "Online":
ret = ({ theme }: { theme: DefaultTheme }) => theme.colors.online;
break;
case "Loading":
ret = ({ theme }: { theme: DefaultTheme }) => theme.colors.loading;
break;
case "Offline":
ret = ({ theme }: { theme: DefaultTheme }) => theme.colors.offline;
break;
default:
ret = ({ theme }: { theme: DefaultTheme }) => theme.colors.offline;
}
return ret;
}};
`
const CardTitleLink = styled(Link)`
display: flex;
flex-direction: row;
justify-content: center;
align-items: center;
align-self: flex-start;
margin: 0.5rem;
padding-left: 1rem;
`
const CardTitleLinkPlaceholder = styled.div`
display: flex;
flex-direction: row;
justify-content: center;
align-items: center;
align-self: flex-start;
margin: 0.5rem;
padding-left: 1rem;
`
// content visible when reduced
const CardTitle = ({ content, href }: { content: Service, href: string }) => {
let card;
if (href) {
card = (
<CardTitleWrap>
<CardTitleLink href={href}>
{
content.icon ? (
<CardTitleIcon>
<Image alt="icon" src={content.icon} fill sizes={'1.5rem'}/>
</CardTitleIcon>
) : (<></>)
}
<CardTitleText>{content.name}</CardTitleText>
{
<OpenInNewTab>
<OpenInNewTabIcon width="100%" height="100%"/>
</OpenInNewTab>
}
</CardTitleLink>
<CardStatus status={content.status}>{content.status}</CardStatus>
</CardTitleWrap>
)
}
else {
card = (
<CardTitleWrap>
<CardTitleLinkPlaceholder>
{
content.icon ? (
<CardTitleIcon>
<Image alt="icon" src={content.icon} fill sizes={'1.5rem'}/>
</CardTitleIcon>
) : (<></>)
}
<CardTitleText>{content.name}</CardTitleText>
</CardTitleLinkPlaceholder>
<CardStatus status={content.status}>{content.status}</CardStatus>
</CardTitleWrap>
)
}
return card
}
// custom objects for CardDescription
//###################################
// shared properties for all Description objects
const CardDescriptionCommon = css`
text-align: left;
font-size: 0.9rem;
margin: 0.3rem;
`
// content visible when expanded
const CardDescriptionWrap = styled.div`
${CardDescriptionCommon}
padding: 0 1rem;
margin-bottom: 2rem;
overflow: hidden; /* Hide scrollbars */
scrollbar-width: thin;
width: 100%;
p {
margin-top: 0;
margin-bottom: 0.9rem;
}
`
const CardDescriptionExtended = styled.p`
${CardDescriptionCommon}
margin: 0;
margin-bottom: 0.9rem;
width: 100%;
`
const CardDescription = ({ content }: { content: Service }) => {
let ret;
ret = (
<CardDescriptionWrap>
<CardDescriptionExtended>
{content.desc}
</CardDescriptionExtended>
<p>
{content.warn}
</p>
<a href={content.extLink}>
Official Site
</a>
</CardDescriptionWrap>
);
return ret;
}
// exported Card Elements
//#######################
export const ServiceCardMobile = ({ content }: { content: Service }) => {
let card;
// TEMP
if (content.href) {
card = (
<Card>
<CardTitle content={content} href={content.href}/>
<CardDescription content={content}/>
</Card>
)
}
else {
card = (
<Card>
<CardTitle content={content} href={""}/>
<CardDescription content={content}/>
</Card>
)
}
return card;
}

View file

@ -1,262 +0,0 @@
import Link from 'next/link';
import Image from 'next/image';
import styled, { css, DefaultTheme } from 'styled-components';
import { Service, Game } from '../../interfaces/CardTypes';
// needed for Online Status checks
interface OnlinePropType {
status: string;
}
// TODO: remove unneeded exports
// replaces .title
export const PageTitle = styled.h1`
margin: 0;
padding: 0.25rem 1rem;
line-height: 1.15;
font-size: 4rem;
text-align: center;
background-color: ${({ theme }) => theme.colors.background ? theme.colors.background : ""};
border-radius: 15px;
`;
// replaces .description
export const PageDescription = styled.p`
background-color: ${({ theme }) => theme.colors.background ? theme.colors.background : ""};
padding: 0.25rem 0.5rem;
margin: 4rem 2rem;
line-height: 1.5;
font-size: 1.5rem;
text-align: center;
border-radius: 10px;
`;
// replaces .grid
export const PageContentBox = styled.div`
display: flex;
align-items: flex-start;
justify-content: center;
flex-wrap: wrap;
width: 100%;
margin: 5.5rem;
`;
// update for PageContentBox
export const PageContentBoxNew = styled(PageContentBox)`
gap: 2rem 1rem;
`
export const CardStyle = css`
display: flex;
flex-direction: column;
align-items: center;
position: relative;
width: 332px;
height: 240px;
`;
export const CardLink = styled(Link)`
${CardStyle}
`;
export const CardStyleWrap = styled.div`
${CardStyle}
`;
// replaces .card & .contentcard
export const PageCard = styled.div`
margin: 1rem;
padding: 23px 10px;
text-align: center;
color: ${({ theme }) => theme.colors.primary};
background-color: ${({ theme }) => theme.colors.background};
text-decoration: none;
border: 2px solid;
border-radius: 10px;
border-color: ${({ theme }) => theme.colors.primary};
transition: all 0.1s linear;
width: 300px;
height: 200px;
display: flex;
flex-direction: column;
justify-content: space-between;
h2 {
margin: 0 0 1rem 0;
font-size: 1.5rem;
}
p {
margin: 0;
font-size: 1rem;
line-height: 1.5;
}
${CardStyleWrap}:focus,${CardStyleWrap}:active,${CardStyleWrap}:hover & {
color: ${({ theme }) => theme.colors.secondary};
border-color: ${({ theme }) => theme.colors.secondary};
background-color: ${({ theme }) => theme.colors.backgroundAlt ? theme.colors.backgroundAlt : theme.colors.background};
}
${CardLink}:focus,${CardLink}:active,${CardLink}:hover & {
color: ${({ theme }) => theme.colors.secondary};
border-color: ${({ theme }) => theme.colors.secondary};
background-color: ${({ theme }) => theme.colors.backgroundAlt ? theme.colors.backgroundAlt : theme.colors.background};
}
`;
// replaces the three status classes
export const OnlineStatus = styled.p<OnlinePropType>`
color: ${props => {
let ret;
switch (props.status) {
case "Online":
ret = ({ theme }: { theme: DefaultTheme }) => theme.colors.online;
break;
case "Loading":
ret = ({ theme }: { theme: DefaultTheme }) => theme.colors.loading;
break;
case "Offline":
ret = ({ theme }: { theme: DefaultTheme }) => theme.colors.offline;
break;
default:
ret = ({ theme }: { theme: DefaultTheme }) => theme.colors.offline;
}
return ret;
}};
padding: 0.2rem;
border: 1px solid;
border-color: ${({ theme }) => theme.colors.primary};
border-radius: 5px;
width: min-content;
position: absolute;
top: 100; right: 50; bottom: 0; left: 50;
offset-position: bottom 10px;
transition: color 0.15s ease, border-color 0.15s ease;
background-color: ${({ theme }) => theme.colors.background};
background-image: ${({ theme }) => theme.backgroundImage ?
"linear-gradient("
+ theme.colors.background + "," + theme.colors.background +
"), url(" + theme.backgroundImage + ")" : ""};
background-repeat: no-repeat;
background-attachment: fixed;
background-size: cover;
${CardStyleWrap}:focus,${CardStyleWrap}:active,${CardStyleWrap}:hover & {
border-color: ${({ theme }) => theme.colors.secondary};
background-color: ${({ theme }) => theme.colors.backgroundAlt ? theme.colors.backgroundAlt : theme.colors.background};
}
${CardLink}:focus,${CardLink}:active,${CardLink}:hover & {
border-color: ${({ theme }) => theme.colors.secondary};
background-color: ${({ theme }) => theme.colors.backgroundAlt ? theme.colors.backgroundAlt : theme.colors.background};
}
`;
// replaces .cardwarn
export const CardContentWarning = styled.p`
color: ${({ theme }) => theme.colors.secondary};
`;
// replaces .contentIcon
export const CardContentTitleIcon = styled(Image)`
object-fit: "contain";
margin-right: 8px;
aspect-ratio: 1;
height: 28px;
`;
// replaces .contentTitle
export const CardContentTitleWrap = styled.div`
position: relative;
display: flex;
flex-direction: row;
justify-content: center;
align-items: center;
margin-bottom: 1rem;
h2 {
margin: 0;
white-space: nowrap;
}
`;
export const CardContentTitle = ({ content }: { content: Service | Game }) => {
return (
<CardContentTitleWrap>
{
content.icon ? (
<CardContentTitleIcon alt="icon" src={content.icon} width="28" height="28" sizes='10vw'/>
) : (<></>)
}
<h2>{content.name}</h2>
</CardContentTitleWrap>
)
}
// Card Content Component for Games Page
export const CardContentGame = ({ content }: { content: Game }) => {
let ret;
if (content.href) {
ret = (
<CardLink href={content.href}>
<PageCard>
<CardContentTitle content={content} />
<p>{content.desc}</p>
<p>{content.ip}</p>
</PageCard>
{content.status ?
<OnlineStatus status={content.status}>{content.status}</OnlineStatus>
: <></>
}
</CardLink>
)
}
else {
ret = (
<CardStyleWrap>
<PageCard>
<CardContentTitle content={content} />
<p>{content.desc}</p>
<p>{content.ip}</p>
</PageCard>
{content.status ?
<OnlineStatus status={content.status}>{content.status}</OnlineStatus>
: <></>
}
</CardStyleWrap>
)
}
return ret;
}
// Card Content Component for Services Page
export const CardContentService = ({ content }: { content: Service }) => {
let ret;
if (content.href) {
ret = (
<CardLink href={content.href}>
<PageCard>
<CardContentTitle content={content} />
<p>{content.desc}</p>
<CardContentWarning>{content.warn}</CardContentWarning>
</PageCard>
<OnlineStatus status={content.status}>{content.status}</OnlineStatus>
</CardLink>
)
}
else {
ret = (
<CardStyleWrap>
<PageCard>
<CardContentTitle content={content} />
<p>{content.desc}</p>
<CardContentWarning>{content.warn}</CardContentWarning>
</PageCard>
<OnlineStatus status={content.status}>{content.status}</OnlineStatus>
</CardStyleWrap>
)
}
return ret;
}

View file

@ -1,57 +0,0 @@
import styled from 'styled-components'
export const StyledBody = styled.body`
padding: 0;
margin: 0;
font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen,
Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif;
`
interface MobilePropType {
mobile?: number;
}
export const Page = styled.div<MobilePropType>`
width: 100%;
background-color: ${({ theme }) => theme.colors.background};
background-image: ${({ theme }) => theme.backgroundImage ? "url(" + theme.backgroundImage + ")" : ""};
background-repeat: no-repeat;
background-attachment: fixed;
background-size: cover;
background-position: ${ props => props.mobile ? ({ theme }) => theme.backgroundOffset ? theme.backgroundOffset : "60%" : ""};
background-position-y: 0;
`
export const Main = styled.main`
color: ${({ theme }) => theme.colors.primary };
min-height: 100vh;
padding: 1rem 0;
flex: 1;
display: flex;
flex-direction: column;
justify-content: flex-start;
align-items: center;
`
export const Footer = styled.footer`
color: ${({ theme }) => theme.colors.primary };
display: flex;
flex: 1;
padding: 2rem 0;
border-top: 1px solid ${({ theme }) => theme.colors.primary };
justify-content: center;
align-items: center;
a {
display: flex;
justify-content: center;
align-items: center;
flex-grow: 1;
}
`
export const MobileFooter = styled(Footer)`
white-space: nowrap;
flex: 0.5;
width: 100%;
`

View file

@ -1,45 +0,0 @@
import styled from 'styled-components'
import Link from 'next/link';
interface ActivePropType {
active?: number;
}
export const NavWrap = styled.div`
display: flex;
flex-wrap: nowrap;
justify-content: center;
align-items: center;
border-bottom: 1px solid ${({ theme }) => theme.colors.primary};
`
export const NavBar = styled.nav`
margin-right: 1%;
display: flex;
flex: 1;
padding: 1rem 0;
flex-wrap: nowrap;
justify-content: center;
align-items: center;
`
export const NavLink = styled(Link) <ActivePropType>`
color: ${props => ({ theme }) => props.active ?
theme.colors.text ?
theme.colors.text : theme.colors.secondary :
theme.colors.primary};
background-color: ${props => ({ theme }) => theme.colors.background};
padding: 2px 6px;
border: 2px solid;
margin: 0.2rem;
border-radius: 5px;
display: flex;
transition: all 0.1s ease;
&:hover {
color: ${({ theme }) => theme.colors.text ? theme.colors.text : theme.colors.secondary};
}
`

View file

@ -1,199 +0,0 @@
import styled from 'styled-components'
import Link from 'next/link';
import { NavBar, NavLink, NavWrap } from './desktop';
interface ActivePropType {
active?: number;
}
interface MultipliesPropType {
num?: number;
}
export const NavWrapMobile = styled(NavWrap)`
width: 100%;
flex-direction: row;
position: fixed;
z-index: 50;
`
export const NavWrapMobileGhost = styled.div`
position: relative;
width: 100%;
height: 50px;
`
export const NavSideMenu = styled.div <ActivePropType>`
position: fixed;
top: 0%; left: 0%; right: 0%; bottom: 0%;
max-width: ${props => props.active ? "240px" : "0px"};
max-height: ${props => props.active ? "100%" : "50px"};
display: flex;
justify-content: space-between;
flex-direction: column;
height: 100%;
z-index: 100;
border-right: ${ props => ({ theme }) => {
let ret: string;
if(props.active) {
ret = "1px solid " + theme.colors.primary;
}
else {
ret = "0px solid";
}
return ret;
}};
background-color: ${ props => ({ theme }) => {
let ret: string;
if (props.active) {
ret = theme.colors.background;
}
else {
ret = "";
}
return ret;
}};
backdrop-filter: ${props => props.active ? "blur(5px)" : ""};
overflow-x: hidden;
transition-property: max-width, max-height, border-right, background-color, backdrop-filter;
transition-timing-function: ease-in-out;
transition-duration: 0.15s, 0s;
transition-delay: ${props => props.active ? "0s" : "0s, 0.15s"};
`
export const NavSideMenuPanel = styled.div <ActivePropType>`
height: 100%;
width: 240px;
overflow: hidden;
display: flex;
flex-direction: column;
align-items: left;
`
export const NavSideMenuButton = styled.button <ActivePropType>`
position: ${ props => props.active ? "absolute" : "fixed"};
z-index: 200;
left: ${ props => props.active ? "165px" : "0px"};
transition-property: left, color, background-color, border-color;
transition-timing-function: ease-in-out;
transition-duration: 0.135s, 0.15s;
transition-delay: ${props => props.active ? "0.03s, 0s" : "0s, 0s"};
align-self: flex-end;
cursor: pointer;
margin: 12px;
color: ${props => ({ theme }) => {
let ret: string;
if (props.active) {
ret = theme.colors.secondary;
}
else {
ret = theme.colors.primary;
}
return ret;
}};
background-color: ${props => ({ theme }) => {
let ret: string;
if (props.active) {
ret = theme.colors.secondary;
}
else {
ret = theme.colors.background;
}
return ret;
}};
border: 2px solid;
border-radius: 5px;
border-color: ${props => ({ theme }) => {
let ret: string;
if (props.active) {
ret = theme.colors.secondary;
}
else {
ret = theme.colors.primary;
}
return ret;
}};
&:hover {
color: ${({ theme }) => {
return theme.colors.secondary
}};
background-color: ${({ theme }) => {
return theme.colors.background
}};
border-color: ${({ theme }) => {
return theme.colors.secondary;
}};
}
`
export const NavSideMenuGhost = styled.div <MultipliesPropType>`
flex: ${ props => props.num ? props.num * 2 : 2 };
`
export const NavBarMobile = styled(NavBar)`
margin-top: 56px;
flex-direction: column;
align-items: flex-start;
`
export const NavLinkMobile = styled(NavLink)`
display: block;
margin-left: 1rem;
margin-top: 1rem;
padding: 0.5rem;
border: none;
border-top: 2px solid;
width: 80%;
text-align: center;
color: ${props => ({ theme }) => props.active ? theme.colors.secondary : theme.colors.primary};
background-color: ${({ theme }) => theme.colors.backgroundAlt ? theme.colors.backgroundAlt : theme.colors.background};
&:hover {
color: ${({ theme }) => theme.colors.secondary };
background-color: ${({ theme }) => theme.colors.backgroundAlt ? theme.colors.backgroundAlt : theme.colors.background};
}
`
export const NavIndicators = styled.nav`
background-color: ${({ theme }) => theme.colors.background};
background-image: ${({ theme }) => theme.backgroundImage ? "url(" + theme.backgroundImage + ")" : ""};
background-repeat: no-repeat;
background-attachment: fixed;
background-size: cover;
background-position: ${({ theme }) => theme.backgroundOffset ? theme.backgroundOffset : "60%"};
background-position-y: 0;
width: 100%;
display: flex;
flex: 1;
padding: 1rem 0;
flex-wrap: nowrap;
justify-content: center;
align-items: center;
`
export const NavIndicator = styled(Link) <ActivePropType>`
margin: 0.2rem;
border-radius: 50%;
aspect-ratio: 1;
width: 10px;
border: 1px solid;
border-color: ${ props => ({ theme }) => props.active ? theme.colors.primary : theme.colors.primary};
background-color: ${props => ({ theme }) => props.active ? theme.colors.secondary : theme.colors.background};
&:hover {
border-color: ${({ theme }) => theme.colors.primary};
color: ${({ theme }) => theme.colors.primary};
background-color: ${({ theme }) => theme.colors.primary};
}
`

View file

@ -1,91 +0,0 @@
import styled from 'styled-components';
interface DisplayPropType {
focus?: number,
show?: number;
}
interface ActivePropType {
active?: number;
}
export const ThemeDropDown = styled.div`
margin-left: 1%;
min-width: 180px;
color: ${({ theme }) => theme.colors.primary};
`
export const ThemeDropDownButton = styled.button<DisplayPropType>`
width: 160px;
border: 2px solid;
border-radius: 5px;
background-color: ${ props => props.focus ?
({ theme }) => theme.colors.background :
({ theme }) => theme.colors.background};
padding: 2px 6px;
cursor: pointer;
color: ${props => props.focus ?
({ theme }) => theme.colors.text ? theme.colors.text : theme.colors.secondary :
({ theme }) => theme.colors.primary};
transition-property: color, border-bottom-left-radius, border-bottom-right-radius, background-color;
transition-timing-function: ease;
transition-duration: 0.15s;
transition-delay: 0s, ${ props => props.show ? "0s, 0s" : "0.6s, 0.6s" }, 0s;
&:focus,:hover {
color: ${({ theme }) => theme.colors.text ? theme.colors.text : theme.colors.secondary};
background-color: ${({ theme }) => theme.colors.background};
}
border-bottom-left-radius: ${ props => props.show ? "0" : "" };
border-bottom-right-radius: ${ props => props.show ? "0" : "" };
`
export const ThemeDropDownOptions = styled.div<DisplayPropType>`
position: absolute;
color: ${({ theme }) => theme.colors.primary};
background-image: ${({ theme }) => theme.backgroundImage ?
"linear-gradient("
+ theme.colors.background + "," + theme.colors.background +
"), url(" + theme.backgroundImage + ")" : ""};
background-repeat: no-repeat;
background-attachment: fixed;
background-size: cover;
background-color: ${({ theme }) => theme.colors.background };
display: flex;
flex-direction: column;
min-width: 160px;
border: 1px solid;
border-top: 0;
border-radius: 5px;
border-top-left-radius: 0px; border-top-right-radius: 0px;
z-index: 1;
overflow-x: hidden;
overflow-y: ${ props => props.show ? "scroll" : "hidden" };
scrollbar-width: none;
max-height: ${ props => props.show ? "20%" : "0%" };
visibility: ${ props => props.show ? "visible" : "hidden" };
transition-property: max-height, visibility;
transition-timing-function: ease-in-out;
transition-duration: 0.6s, 0s;
transition-delay: 0s, ${ props => props.show ? "0s" : "0.5s" };
`
export const ThemeDropDownOption = styled.button<ActivePropType>`
color: ${ props => props.active ? ({ theme }) => theme.colors.secondary : ({ theme }) => theme.colors.primary };
background-color: transparent;
cursor: pointer;
align-self: center;
border: 0px solid;
padding: 0.2rem 0.5rem;
text-decoration: none;
width: 90%;
&:hover {
color: ${({ theme }) => theme.colors.secondary};
}
`

View file

@ -1,49 +0,0 @@
import styled from 'styled-components';
import { ThemeDropDown, ThemeDropDownButton, ThemeDropDownOption, ThemeDropDownOptions } from './desktop';
export const ThemeDropDownMobile = styled(ThemeDropDown)`
width: 80%;
margin-left: 1rem;
`;
export const ThemeDropDownButtonMobile = styled(ThemeDropDownButton)`
width: 100%;
padding: 0.5rem;
border: none;
border-top: 2px solid;
border-left: ${ props => props.show ? "2px solid" : "0px solid"};
border-right: ${ props => props.show ? "2px solid" : "0px solid"};
color: ${props => ({ theme }) => props.focus ? theme.colors.secondary : theme.colors.primary};
background-color: ${({ theme }) => theme.colors.backgroundAlt ? theme.colors.backgroundAlt : theme.colors.background};
&:focus,:hover {
color: ${({ theme }) => theme.colors.secondary};
background-color: ${({ theme }) => theme.colors.backgroundAlt ? theme.colors.backgroundAlt : theme.colors.background};
}
transition-property: color, border-bottom-left-radius, border-bottom-right-radius, background-color, border-left, border-right;
transition-timing-function: ease;
transition-duration: 0.15s;
transition-delay: 0s, ${ props => props.show ? "0s" : "0.6s, 0.6s, 0s, 0.6s, 0.6s" };
`;
export const ThemeDropDownOptionsMobile = styled(ThemeDropDownOptions)`
background-image: unset;
background-color: transparent;
border: 2px solid ${props => ({ theme }) => props.focus ? theme.colors.secondary : theme.colors.primary};
border-top: 0;
max-height: ${ props => props.show ? "100%" : "0%"};
position: relative;
width: 100%;
`;
export const ThemeDropDownOptionMobile = styled(ThemeDropDownOption)`
text-align: left;
margin: 0.5rem;
padding: 0rem 0.5rem;
width: 80%;
border-left: 2px solid;
border-radius: 5px;
`;

View file

@ -1,59 +0,0 @@
// Probably a good idea to spread this out into multiple files under a folder once it gets bigger
import { createGlobalStyle, DefaultTheme } from 'styled-components'
export const GlobalStyle = createGlobalStyle`
html,
body {
padding: 0;
margin: 0;
font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen,
Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif;
}
a {
color: inherit;
text-decoration: none;
}
* {
box-sizing: border-box;
}
`
export const lightTheme: DefaultTheme = {
themeName: "Light Theme",
themeId: 0,
colors: {
background: '#ffffff',
primary: '#00aaff',
secondary:'#ff5300',
online: '#00ff00',
loading: '#ff5300',
offline: '#ff0000',
},
}
export const darkTheme: DefaultTheme = {
themeName: "Dark Theme",
themeId: 1,
colors: {
background: '#1f1f1f',
primary: '#00aaff',
secondary:'#ff5300',
online: '#00ff00',
loading: '#ff5300',
offline: '#ff0000',
},
}
export const amoledTheme: DefaultTheme = {
themeName: "AMOLED Theme",
themeId: 2,
colors: {
background: '#000000',
primary: '#00aaff',
secondary:'#ff5300',
online: '#00ff00',
loading: '#ff5300',
offline: '#ff0000',
},
}

View file

@ -1,99 +0,0 @@
import { useUpdateTheme } from "../pages/_app";
import { useContext, useState } from 'react';
import { ThemeContext, DefaultTheme } from "styled-components";
import { darkTheme, lightTheme } from './themes';
import { ThemeDropDown, ThemeDropDownButton, ThemeDropDownOption, ThemeDropDownOptions } from "./styles/themedropdown/desktop";
import { ThemeDropDownMobile, ThemeDropDownButtonMobile, ThemeDropDownOptionMobile, ThemeDropDownOptionsMobile } from "./styles/themedropdown/mobile";
import Themes from '../public/data/themes.json';
export const StyleSelector = ({ mobile }: { mobile: number }) => {
const themes: DefaultTheme[] = Themes.themes;
const updateTheme = useUpdateTheme();
const currentTheme = useContext(ThemeContext);
const [selectedTheme, setSelectedTheme] = useState(themes[currentTheme.themeId]);
if(currentTheme !== selectedTheme) {
setSelectedTheme(currentTheme);
}
const updateThemeWithStorage = (newTheme: DefaultTheme) => {
if (newTheme.themeId === lightTheme.themeId) {
updateLightTheme(newTheme);
}
else {
setSelectedTheme(newTheme);
localStorage.setItem("theme", newTheme.themeId.toString());
updateTheme(newTheme);
}
}
const updateLightTheme = (newTheme: DefaultTheme) => {
if (confirm("Really switch to Light Mode?")) {
setSelectedTheme(newTheme);
localStorage.setItem("theme", newTheme.themeId.toString());
updateTheme(newTheme)
}
}
const [visible, setVisible] = useState(false);
const [buttonFocus, setButtonFocus] = useState(visible);
function handleBlur(event:any) {
if (!event.currentTarget.contains(event.relatedTarget)) {
setButtonFocus(false);
setVisible(false);
}
}
let themeselector: JSX.Element;
if(mobile) {
themeselector = (
<ThemeDropDownMobile onBlur={(event) => handleBlur(event)}>
<ThemeDropDownButtonMobile focus={+buttonFocus} show={+visible} onFocus={() => setButtonFocus(true)} onClick={() => setVisible(visible => !visible)}>
{selectedTheme.themeName}
</ThemeDropDownButtonMobile>
<ThemeDropDownOptionsMobile focus={+buttonFocus} id="themesDropdown" show={+visible}>
{themes.map((theme) => (
<ThemeDropDownOptionMobile active={theme.themeId === selectedTheme.themeId ? 1 : 0} key={theme.themeId} onClick={() => updateThemeWithStorage(theme)}>
{theme.themeName}
</ThemeDropDownOptionMobile>
))}
</ThemeDropDownOptionsMobile>
</ThemeDropDownMobile>
);
}
else {
themeselector = (
<ThemeDropDown onBlur={(event) => handleBlur(event)}>
<ThemeDropDownButton focus={+buttonFocus} show={+visible} onFocus={() => setButtonFocus(true)} onClick={() => setVisible(visible => !visible)}>{selectedTheme.themeName}
</ThemeDropDownButton>
<ThemeDropDownOptions id="themesDropdown" show={+visible}>
{themes.map((theme) => (
<ThemeDropDownOption active={theme.themeId === selectedTheme.themeId ? 1 : 0} key={theme.themeId} onClick={() => updateThemeWithStorage(theme)}>
{theme.themeName}
</ThemeDropDownOption>
))}
</ThemeDropDownOptions>
</ThemeDropDown>
);
}
return themeselector;
}
export const StyleSelectorPlaceholder = () => {
return (
<ThemeDropDown></ThemeDropDown>
)
}
export function getTheme(themeId: number, themes: DefaultTheme[]): DefaultTheme {
let retTheme: DefaultTheme = darkTheme;
themes.forEach((theme) => {
if (theme.themeId === themeId) { retTheme = theme};
})
return retTheme;
}
export default StyleSelector;

View file

@ -1,34 +0,0 @@
import { useEffect, useState } from "react";
interface ScreenSize {
width: number | undefined;
height: number | undefined;
}
export default function useWindowSize(): number {
const [windowSize, setWindowSize] = useState<ScreenSize>({
width: undefined,
height: undefined,
});
useEffect(() => {
function handleResize() {
setWindowSize({
width: window.innerWidth,
height: window.innerHeight,
});
}
window.addEventListener("resize", handleResize);
handleResize();
return () => window.removeEventListener("resize", handleResize);
}, []);
if(typeof(windowSize.width) === "number") {
return windowSize.width <= 1080 ? 1 : 0;
}
else {
return 0;
}
}

View file

@ -1,44 +0,0 @@
export interface EntryList {
services: Service[],
games: Game[]
}
export interface Game {
name: string,
icon: string,
href: string,
desc: string,
ip: string,
status: Status,
}
export interface Service {
name: string,
icon?: string,
href?: string,
desc: string,
warn?: string,
extLink?: string,
type: ServiceType,
docker_container_name: string,
location: ServiceLocation,
status: Status,
}
export enum Status {
online = "Online",
offline = "Offline",
loading = "Loading",
error = "ERROR"
}
export enum ServiceLocation {
brr7_4800u = "brr7-4800u",
tower_0 = "tower-0",
other = ""
}
export enum ServiceType {
docker = "docker",
app = "app"
}

View file

@ -1,11 +0,0 @@
import { ServiceLocation } from './CardTypes';
export interface DockerInfo {
name: string,
status: DockerStatus,
id: string
location: ServiceLocation,
}
export enum DockerStatus {
running = "running",
}

View file

@ -1,19 +0,0 @@
/** @type {import('next').NextConfig} */
const nextConfig = {
reactStrictMode: true,
output: 'standalone',
compiler: {
styledComponents: true,
},
webpack(config) {
config.module.rules.push({
test: /\.svg$/,
use: [{ loader: "@svgr/webpack", options: { icon: true } }]
});
return config;
}
};
module.exports = nextConfig

View file

@ -1,32 +1,46 @@
{
"name": "main-site",
"version": "0.5.7",
"private": true,
"scripts": {
"dev:debug": "NODE_OPTIONS='--inspect' next dev -H :: -p 8001",
"dev": "next dev -H :: -p 8001",
"build": "next build",
"start": "next start -H :: -p 8001",
"lint": "next lint"
},
"dependencies": {
"@svgr/webpack": "^6.5.1",
"eslint-config": "^0.3.0",
"next": "^13.0.6",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-is": "^18.2.0",
"sharp": "^0.31.2",
"styled-components": "^5.3.6",
"swr": "^1.3.0"
},
"devDependencies": {
"@types/cookie": "^0.5.1",
"@types/dockerode": "^3.3.14",
"@types/react": "^18.0.14",
"@types/styled-components": "^5.1.26",
"eslint": "^8.23.1",
"eslint-config-next": "12.2.0",
"typescript": "^4.7.4"
}
"name": "main-site",
"version": "0.0.1",
"private": true,
"scripts": {
"dev": "vite dev",
"build": "vite build",
"preview": "vite preview",
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
"lint": "prettier --check . && eslint .",
"format": "prettier --write .",
"ui": "npx shadcn-svelte@latest"
},
"devDependencies": {
"@sveltejs/adapter-auto": "^3.0.0",
"@sveltejs/kit": "^2.0.0",
"@sveltejs/vite-plugin-svelte": "^3.0.0",
"@types/eslint": "8.56.0",
"@typescript-eslint/eslint-plugin": "^6.0.0",
"@typescript-eslint/parser": "^6.0.0",
"autoprefixer": "^10.4.16",
"eslint": "^8.56.0",
"eslint-config-prettier": "^9.1.0",
"eslint-plugin-svelte": "^2.35.1",
"postcss": "^8.4.32",
"postcss-load-config": "^5.0.2",
"prettier": "^3.1.1",
"prettier-plugin-svelte": "^3.1.2",
"prettier-plugin-tailwindcss": "^0.5.9",
"svelte": "^5.0.0-next.1",
"svelte-check": "^3.6.0",
"tailwindcss": "^3.3.6",
"tslib": "^2.4.1",
"typescript": "^5.0.0",
"vite": "^5.0.3"
},
"type": "module",
"dependencies": {
"bits-ui": "^0.13.0",
"clsx": "^2.1.0",
"radix-icons-svelte": "^1.2.1",
"tailwind-merge": "^2.2.0",
"tailwind-variants": "^0.1.19"
}
}

View file

@ -1,58 +0,0 @@
import '/styles/globals.css'
import { Fragment, ReactElement, ReactNode } from 'react'
import Layout from '../components/layout'
import type { NextPage } from 'next'
import { AppProps } from 'next/app';
import { DefaultTheme, ThemeProvider } from 'styled-components';
import { createContext, useContext, useEffect, useState } from 'react'
import { darkTheme, GlobalStyle } from '../components/themes';
import { getTheme } from '../components/themeselector';
import Themes from '../public/data/themes.json';
export type NextPageWithLayout<P = {}, IP = P> = NextPage<P, IP> & {
getLayout?: (page: ReactElement) => ReactNode
}
export type AppPropsWithLayout = AppProps & {
Component: NextPageWithLayout
}
export const ThemeUpdateContext = createContext(
(theme: DefaultTheme) => console.error("attempted to set theme outside of a ThemeUpdateContext.Provider")
)
export const useUpdateTheme = () => useContext(ThemeUpdateContext);
export default function Website({ Component, pageProps }: AppPropsWithLayout) {
const loadedThemes = Themes.themes;
const [selectedTheme, setselectedTheme] = useState(darkTheme);
const [themes, setThemes] = useState(loadedThemes);
useEffect(() => {
const storedThemeIdTemp = localStorage.getItem("theme");
// get stored theme data
// if theme data differs set it
// if not just exit
if (storedThemeIdTemp && parseInt(storedThemeIdTemp) !== selectedTheme.themeId) {
setselectedTheme(getTheme(parseInt(storedThemeIdTemp), themes))
}
}, [selectedTheme, themes])
// Use the layout defined at the page level, if available
const getLayout = Component.getLayout ?? ((page) => (
<Layout>{page}</Layout>))
return (
<Fragment>
<GlobalStyle />
<ThemeProvider theme={selectedTheme}>
<ThemeUpdateContext.Provider value={setselectedTheme}>
{getLayout(<Component {...pageProps} />)}
</ThemeUpdateContext.Provider>
</ThemeProvider>
</Fragment>
)
}

View file

@ -1,15 +0,0 @@
import { Html, Head, Main, NextScript } from 'next/document'
import { StyledBody } from '../components/styles/generic'
export default function Document() {
return (
<Html lang='en'>
<Head />
<StyledBody>
<Main />
<NextScript />
</StyledBody>
</Html>
)
}

View file

@ -1,22 +0,0 @@
import Head from 'next/head'
import { PageDescription, PageTitle } from '../components/styles/content'
export default function About() {
return (
<>
<Head>
<title>Neshweb - About</title>
<meta charSet='utf-8' />
<link rel="icon" href="/favicon.ico" />
</Head>
<PageTitle>
About
</PageTitle>
<PageDescription>
I&apos;m currently expanding what I want to do with this site.
Currently a list of available services and servers is available via the respective navbar entry.
</PageDescription>
</>
)
}

View file

@ -1,33 +0,0 @@
import ApiSecret from '../../private/portainer_api_secret.json'
import { DockerInfo } from '../../interfaces/DockerStatus';
import { ServiceLocation } from '../../interfaces/CardTypes';
export default async function ContainersAPI(req: any, res: any) {
const token = JSON.parse(JSON.stringify(ApiSecret.token));
try {
const res1 = await fetch('https://portainer.neshweb.net/api/endpoints/2/docker/containers/json', {
method: "GET",
headers: {"X-API-Key": token}
});
const unparsed = await res1.json();
let list: DockerInfo[] = [];
unparsed.forEach((entry: any) => {
let newEntry = {} as DockerInfo;
newEntry.name = entry.Names[0].substring(1);
newEntry.status = entry.State;
newEntry.id = entry.Id;
newEntry.location = ServiceLocation.tower_0;
list.push(newEntry);
});
res.status(200).json(list);
}
catch (error) {
console.log(error);
res.status(500).json({ error: 'Error reading data' });
}
}

View file

@ -1,44 +0,0 @@
import Head from 'next/head'
import Link from 'next/link';
import { PageTitle, PageDescription, PageContentBox, PageCard, CardLink } from '../components/styles/content';
export default function Home() {
return (
<>
<Head>
<title>Neshweb - Home</title>
<meta charSet='utf-8' />
<link rel="icon" href="/favicon.ico" />
</Head>
<PageTitle>
Welcome to my Servers Webpage
</PageTitle>
<PageDescription>
Feel free to look around
</PageDescription>
<PageContentBox>
<CardLink key="about" href="/about">
<PageCard>
<h2>About &rarr;</h2>
<p>Useless Info, don&apos;t bother</p>
</PageCard>
</CardLink>
<CardLink key="servers" href="/games">
<PageCard>
<h2>Games &rarr;</h2>
<p>List of all available Servers</p>
</PageCard>
</CardLink>
<CardLink key="services" href="/services">
<PageCard>
<h2>Services &rarr;</h2>
<p>List of available Services</p>
</PageCard>
</CardLink>
</PageContentBox>
</>
)
}

View file

@ -1,34 +0,0 @@
import Head from 'next/head'
import { Game } from '../interfaces/CardTypes';
import { PageContentBox, PageDescription, PageTitle, CardContentGame } from '../components/styles/content'
import GameList from '../public/data/pages.json';
function Servers() {
// TODO: unuggly this shit
const serverList: Game[] = JSON.parse(JSON.stringify(GameList.games));
return (
<>
<Head>
<title>Neshweb - Servers</title>
<meta charSet='utf-8' />
<link rel="icon" href="/favicon.ico" />
</Head>
<PageTitle>
Server List
</PageTitle>
<PageDescription>
Lists all available Services, probably up-to-date
</PageDescription>
<PageContentBox>
{Object.values(serverList).map((item: Game) => (
<CardContentGame key={item.name} content={item} />
))}
</PageContentBox>
</>
)
}
export default Servers

View file

@ -1,180 +0,0 @@
import Head from 'next/head'
import { Service, Status, ServiceType } from '../interfaces/CardTypes';
import { ReactElement } from 'react'
import useSWR from 'swr';
import ServiceList from '../public/data/pages.json';
import { DockerInfo } from '../interfaces/DockerStatus';
import { PageContentBox, PageDescription, PageTitle } from '../components/styles/content';
import useWindowSize from '../components/windowsize';
import { ServiceCardMobile } from '../components/styles/cards/mobile';
import { ServiceCardDesktop } from '../components/styles/cards/desktop';
const fetcher = (url: string) => fetch(url).then((res) => res.json())
function Services() {
const { initialData, fullData, loadingFull, error } = useServices();
const isMobile = useWindowSize();
let content: ReactElement = <></>;
if (error) { content = <div>Error loading data</div> }
else if (loadingFull) {
if (isMobile) {
content =
<PageContentBox>
{initialData?.map((item: Service) => (
<ServiceCardMobile key={item.name} content={item} />
))}
</PageContentBox>
}
else {
content =
<PageContentBox>
{initialData?.map((item: Service) => (
<ServiceCardDesktop key={item.name} content={item} />
))}
</PageContentBox>
}
}
else if (fullData) {
if (isMobile) {
content =
<PageContentBox>
{fullData.map((item: Service) => (
<ServiceCardMobile key={item.name} content={item} />
))}
</PageContentBox>
}
else {
content =
<PageContentBox>
{fullData.map((item: Service) => (
<ServiceCardDesktop key={item.name} content={item} />
))}
</PageContentBox>
}
}
else {
content = <div>Error loading data</div>
}
return (
<>
<Head>
<title>Neshweb - Services</title>
<meta charSet='utf-8' />
<link rel="icon" href="/favicon.ico" />
<meta name="description" content="Lists all available Services, most likely up-to-date" />
</Head>
<PageTitle>
Service List
</PageTitle>
<PageDescription>
Lists all available Services, most likely up-to-date
</PageDescription>
{content}
</>
)
}
async function getStatus(entry: Service, containers: DockerInfo[]) {
// Currently the only location supporting different fetching depending on type is brr7-4800u
// Others to follow but low prio as this is currently the only location used
// Type APP
if (entry.type === ServiceType.app && entry.href) {
await fetch(entry.href)
.then((response) => {
if (response.ok) {
switch (response.status) {
case 200:
case 301:
case 302:
entry.status = Status.online;
break;
default:
entry.status = Status.offline;
}
}
else {
entry.status = Status.offline;
}
})
.catch((error) => {
console.error("Error pinging Website: ", error);
entry.status = Status.error;
})
}
// Type Docker
else if (entry.type === ServiceType.docker) {
if (entry.name !== null) {
let found = false;
for (let i = 0; i < containers.length; i++) {
const container = containers[i];
// Docker API returns container names with / prepended
if (container.name === entry.docker_container_name) {
if (container.location === entry.location) {
// so far only "running" is properly implemented, mroe cases to follow as needed
switch (container.status) {
case "running":
entry.status = Status.online;
break;
default:
console.log("Container Status " + container.status + " has no case implemented");
entry.status = Status.offline;
}
found = true;
// cancel the for
break;
}
}
// If container name is not missing the container is set to offline
else {
entry.status = Status.offline;
}
}
if (!found) {
console.warn("Container for " + entry.name + " could not be found");
}
}
// if name is null do not enter for loop
else {
console.error("Container Name not specified");
entry.status = Status.error;
}
}
// If no Type matches
else {
console.warn("Service Type for Service " + entry.name + " not specified or invalid");
entry.status = Status.error;
}
return entry;
}
const fetchFullDataArray = (containerData: DockerInfo[], dataSet: Service[]) => {
const fetchStatus = (entry: Service) => getStatus(entry, containerData);
return Promise.all(dataSet.map(fetchStatus));
}
function useServices() {
const { data: containerData, error: containerError } = useSWR('/api/containers', fetcher);
// TODO: unfuck this
const initialData: Service[] = JSON.parse(JSON.stringify(ServiceList.services));
initialData.forEach((service) => {
if (service.status === undefined) service.status = Status.loading;
})
const { data: fullData, error: fullError } = useSWR((containerData) ? [containerData, initialData] : null, fetchFullDataArray)
const loadingFull = !fullData && !fullError
return {
initialData,
fullData,
loadingFull,
error: fullError || containerError,
};
}
export default Services

20
styled.d.ts vendored
View file

@ -1,20 +0,0 @@
import 'styled-components'
declare module 'styled-components' {
export interface DefaultTheme {
themeName: string,
themeId: number,
backgroundImage?: string,
backgroundOffset?: string,
colors: {
background: string,
backgroundAlt?: string,
primary: string,
secondary: string,
text?: string,
online: string,
loading: string,
offline: string,
}
}
}

View file

@ -1 +0,0 @@

View file

@ -1 +0,0 @@

View file

@ -1,36 +1,18 @@
{
"compilerOptions": {
"target": "esnext",
"lib": [
"dom",
"dom.iterable",
"esnext"
],
"allowJs": true,
"skipLibCheck": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"noEmit": true,
"incremental": true,
"esModuleInterop": true,
"module": "esnext",
"moduleResolution": "nodenext",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "preserve",
"plugins": [
{
"name": "next"
}
]
},
"include": [
"next-env.d.ts",
"**/*.ts",
"**/*.tsx",
".next/types/**/*.ts"
],
"exclude": [
"node_modules"
]
"extends": "./.svelte-kit/tsconfig.json",
"compilerOptions": {
"allowJs": true,
"checkJs": true,
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"resolveJsonModule": true,
"skipLibCheck": true,
"sourceMap": true,
"strict": true,
"moduleResolution": "bundler"
}
// Path aliases are handled by https://kit.svelte.dev/docs/configuration#alias
//
// If you want to overwrite includes/excludes, make sure to copy over the relevant includes/excludes
// from the referenced tsconfig.json - TypeScript does not merge them in
}

5472
yarn.lock

File diff suppressed because it is too large Load diff