fix: Work more on TOTP, renamed homepage to dashboard and began improvements

This commit is contained in:
LukeGus
2025-10-15 23:48:47 -05:00
parent 57cfea2ca8
commit caa270c77d
12 changed files with 314 additions and 203 deletions

View File

@@ -13,7 +13,7 @@ import { getReleasesRSS, getVersionInfo } from "@/ui/main-axios.ts";
import { useTranslation } from "react-i18next";
import { BookOpen, X } from "lucide-react";
interface HomepageUpdateLogProps extends React.ComponentProps<"div"> {
interface UpdateLogProps extends React.ComponentProps<"div"> {
loggedIn: boolean;
}
@@ -59,7 +59,7 @@ interface VersionResponse {
cache_age?: number;
}
export function HomepageUpdateLog({ loggedIn }: HomepageUpdateLogProps) {
export function UpdateLog({ loggedIn }: UpdateLogProps) {
const { t } = useTranslation();
const [releases, setReleases] = useState<RSSResponse | null>(null);
const [versionInfo, setVersionInfo] = useState<VersionResponse | null>(null);

View File

@@ -0,0 +1,204 @@
import React, { useEffect, useState } from "react";
import { Auth } from "@/ui/Desktop/Authentication/Auth.tsx";
import { UpdateLog } from "@/ui/Desktop/Apps/Dashboard/Apps/UpdateLog.tsx";
import { AlertManager } from "@/ui/Desktop/Apps/Dashboard/Apps/Alerts/AlertManager.tsx";
import { Button } from "@/components/ui/button.tsx";
import { getUserInfo, getDatabaseHealth, getCookie } from "@/ui/main-axios.ts";
import { useSidebar } from "@/components/ui/sidebar.tsx";
import { Separator } from "@/components/ui/separator.tsx";
import { ChartLine, History } from "lucide-react";
import { Status } from "@/components/ui/shadcn-io/status";
interface DashboardProps {
onSelectView: (view: string) => void;
isAuthenticated: boolean;
authLoading: boolean;
onAuthSuccess: (authData: {
isAdmin: boolean;
username: string | null;
userId: string | null;
}) => void;
isTopbarOpen: boolean;
}
export function Dashboard({
isAuthenticated,
authLoading,
onAuthSuccess,
isTopbarOpen,
}: DashboardProps): React.ReactElement {
const [loggedIn, setLoggedIn] = useState(isAuthenticated);
const [, setIsAdmin] = useState(false);
const [, setUsername] = useState<string | null>(null);
const [userId, setUserId] = useState<string | null>(null);
const [dbError, setDbError] = useState<string | null>(null);
let sidebarState: "expanded" | "collapsed" = "expanded";
try {
const sidebar = useSidebar();
sidebarState = sidebar.state;
} catch {}
const topMarginPx = isTopbarOpen ? 74 : 26;
const leftMarginPx = sidebarState === "collapsed" ? 26 : 8;
const bottomMarginPx = 8;
useEffect(() => {
setLoggedIn(isAuthenticated);
}, [isAuthenticated]);
useEffect(() => {
if (isAuthenticated) {
if (getCookie("jwt")) {
getUserInfo()
.then((meRes) => {
setIsAdmin(!!meRes.is_admin);
setUsername(meRes.username || null);
setUserId(meRes.userId || null);
setDbError(null);
})
.catch((err) => {
setIsAdmin(false);
setUsername(null);
setUserId(null);
const errorCode = err?.response?.data?.code;
if (errorCode === "SESSION_EXPIRED") {
console.warn("Session expired - please log in again");
setDbError("Session expired - please log in again");
} else {
setDbError(null);
}
});
getDatabaseHealth()
.then(() => {
setDbError(null);
})
.catch((err) => {
if (err?.response?.data?.error?.includes("Database")) {
setDbError(
"Could not connect to the database. Please try again later.",
);
}
});
}
}
}, [isAuthenticated]);
return (
<>
{!loggedIn ? (
<div className="w-full h-full flex items-center justify-center">
<Auth
setLoggedIn={setLoggedIn}
setIsAdmin={setIsAdmin}
setUsername={setUsername}
setUserId={setUserId}
loggedIn={loggedIn}
authLoading={authLoading}
dbError={dbError}
setDbError={setDbError}
onAuthSuccess={onAuthSuccess}
/>
</div>
) : (
<div
className="bg-dark-bg text-white rounded-lg border-2 border-dark-border overflow-hidden flex"
style={{
marginLeft: leftMarginPx,
marginRight: 17,
marginTop: topMarginPx,
marginBottom: bottomMarginPx,
height: `calc(100vh - ${topMarginPx + bottomMarginPx}px)`,
}}
>
<div className="flex flex-col relative z-10 w-full h-full">
<div className="flex flex-row items-center justify-between w-full px-3 mt-3">
<div className="text-2xl text-white font-semibold">Dashboard</div>
<div className="flex flex-row gap-3">
<Button
className="font-semibold"
variant="outline"
onClick={() =>
window.open(
"https://github.com/Termix-SSH/Termix",
"_blank",
)
}
>
GitHub
</Button>
<Button
className="font-semibold"
variant="outline"
onClick={() =>
window.open(
"https://github.com/Termix-SSH/Support/issues/new",
"_blank",
)
}
>
Support
</Button>
<Button
className="font-semibold"
variant="outline"
onClick={() =>
window.open(
"https://discord.com/invite/jVQGdvHDrf",
"_blank",
)
}
>
Discord
</Button>
<Button
className="font-semibold"
variant="outline"
onClick={() =>
window.open("https://github.com/sponsors/LukeGus", "_blank")
}
>
Donate
</Button>
</div>
</div>
<Separator className="mt-3 p-0.25" />
<div className="flex flex-col h-screen my-5 mx-5 gap-4">
<div className="flex flex-row flex-1 gap-4">
<div className="flex-1 border-2 border-dark-border rounded-md bg-dark-bg-darker">
<div className="flex flex-col mx-3 my-2">
<p className="text-xl font-semibold mb-3 flex flex-row">
<ChartLine className="mr-3" />
Server Status
</p>
<div className="flex flex-row items-center">
<History color="#7393B3" />
<p className="ml-3">Version</p>
</div>
</div>
</div>
<div className="flex-1 border-2 border-dark-border rounded-md bg-dark-bg-darker">
test
</div>
</div>
<div className="flex flex-row flex-1 gap-4">
<div className="flex-1 border-2 border-dark-border rounded-md bg-dark-bg-darker">
test
</div>
<div className="flex-1 border-2 border-dark-border rounded-md bg-dark-bg-darker">
test
</div>
</div>
</div>
</div>
</div>
)}
<AlertManager userId={userId} loggedIn={loggedIn} />
</>
);
}

View File

@@ -1,171 +0,0 @@
import React, { useEffect, useState } from "react";
import { Auth } from "@/ui/Desktop/Authentication/Auth.tsx";
import { HomepageUpdateLog } from "@/ui/Desktop/Apps/Homepage/Apps/UpdateLog.tsx";
import { AlertManager } from "@/ui/Desktop/Apps/Homepage/Apps/Alerts/AlertManager.tsx";
import { Button } from "@/components/ui/button.tsx";
import { getUserInfo, getDatabaseHealth, getCookie } from "@/ui/main-axios.ts";
import { useSidebar } from "@/components/ui/sidebar.tsx";
interface HomepageProps {
onSelectView: (view: string) => void;
isAuthenticated: boolean;
authLoading: boolean;
onAuthSuccess: (authData: {
isAdmin: boolean;
username: string | null;
userId: string | null;
}) => void;
isTopbarOpen: boolean;
}
export function Homepage({
isAuthenticated,
authLoading,
onAuthSuccess,
isTopbarOpen,
}: HomepageProps): React.ReactElement {
const [loggedIn, setLoggedIn] = useState(isAuthenticated);
const [, setIsAdmin] = useState(false);
const [, setUsername] = useState<string | null>(null);
const [userId, setUserId] = useState<string | null>(null);
const [dbError, setDbError] = useState<string | null>(null);
let sidebarState: "expanded" | "collapsed" = "expanded";
try {
const sidebar = useSidebar();
sidebarState = sidebar.state;
} catch {}
const topMarginPx = isTopbarOpen ? 74 : 26;
const leftMarginPx = sidebarState === "collapsed" ? 26 : 8;
const bottomMarginPx = 8;
useEffect(() => {
setLoggedIn(isAuthenticated);
}, [isAuthenticated]);
useEffect(() => {
if (isAuthenticated) {
if (getCookie("jwt")) {
getUserInfo()
.then((meRes) => {
setIsAdmin(!!meRes.is_admin);
setUsername(meRes.username || null);
setUserId(meRes.userId || null);
setDbError(null);
})
.catch((err) => {
setIsAdmin(false);
setUsername(null);
setUserId(null);
const errorCode = err?.response?.data?.code;
if (errorCode === "SESSION_EXPIRED") {
console.warn("Session expired - please log in again");
setDbError("Session expired - please log in again");
} else {
setDbError(null);
}
});
getDatabaseHealth()
.then(() => {
setDbError(null);
})
.catch((err) => {
if (err?.response?.data?.error?.includes("Database")) {
setDbError(
"Could not connect to the database. Please try again later.",
);
}
});
}
}
}, [isAuthenticated]);
return (
<>
{!loggedIn ? (
<div className="w-full h-full flex items-center justify-center">
<Auth
setLoggedIn={setLoggedIn}
setIsAdmin={setIsAdmin}
setUsername={setUsername}
setUserId={setUserId}
loggedIn={loggedIn}
authLoading={authLoading}
dbError={dbError}
setDbError={setDbError}
onAuthSuccess={onAuthSuccess}
/>
</div>
) : (
<div
className="bg-dark-bg text-white rounded-lg border-2 border-dark-border overflow-hidden flex items-center justify-center"
style={{
marginLeft: leftMarginPx,
marginRight: 17,
marginTop: topMarginPx,
marginBottom: bottomMarginPx,
height: `calc(100vh - ${topMarginPx + bottomMarginPx}px)`,
}}
>
<div className="flex flex-col items-center justify-center gap-6 relative z-10">
<HomepageUpdateLog loggedIn={loggedIn} />
<div className="flex flex-row items-center gap-3 flex-wrap justify-center">
<Button
variant="outline"
size="sm"
className="text-sm border-dark-border text-gray-300 hover:text-white hover:bg-dark-bg transition-colors"
onClick={() =>
window.open("https://github.com/Termix-SSH/Termix", "_blank")
}
>
GitHub
</Button>
<div className="w-px h-4 bg-dark-border"></div>
<Button
variant="outline"
size="sm"
className="text-sm border-dark-border text-gray-300 hover:text-white hover:bg-dark-bg transition-colors"
onClick={() =>
window.open(
"https://github.com/Termix-SSH/Termix/issues/new",
"_blank",
)
}
>
Feedback
</Button>
<div className="w-px h-4 bg-dark-border"></div>
<Button
variant="outline"
size="sm"
className="text-sm border-dark-border text-gray-300 hover:text-white hover:bg-dark-bg transition-colors"
onClick={() =>
window.open("https://discord.com/invite/jVQGdvHDrf", "_blank")
}
>
Discord
</Button>
<div className="w-px h-4 bg-dark-border"></div>
<Button
variant="outline"
size="sm"
className="text-sm border-dark-border text-gray-300 hover:text-white hover:bg-dark-bg transition-colors"
onClick={() =>
window.open("https://github.com/sponsors/LukeGus", "_blank")
}
>
Donate
</Button>
</div>
</div>
</div>
)}
<AlertManager userId={userId} loggedIn={loggedIn} />
</>
);
}

View File

@@ -25,7 +25,7 @@ import {
} from "../../main-axios.ts";
import { ElectronServerConfig as ServerConfigComponent } from "@/ui/Desktop/Authentication/ElectronServerConfig.tsx";
interface HomepageAuthProps extends React.ComponentProps<"div"> {
interface AuthProps extends React.ComponentProps<"div"> {
setLoggedIn: (loggedIn: boolean) => void;
setIsAdmin: (isAdmin: boolean) => void;
setUsername: (username: string | null) => void;
@@ -51,7 +51,7 @@ export function Auth({
setDbError,
onAuthSuccess,
...props
}: HomepageAuthProps) {
}: AuthProps) {
const { t } = useTranslation();
const [tab, setTab] = useState<"login" | "signup" | "external" | "reset">(
"login",

View File

@@ -1,6 +1,6 @@
import React, { useState, useEffect } from "react";
import { LeftSidebar } from "@/ui/Desktop/Navigation/LeftSidebar.tsx";
import { Homepage } from "@/ui/Desktop/Apps/Homepage/Homepage.tsx";
import { Dashboard } from "@/ui/Desktop/Apps/Dashboard/Dashboard.tsx";
import { AppView } from "@/ui/Desktop/Navigation/AppView.tsx";
import { HostManager } from "@/ui/Desktop/Apps/Host Manager/HostManager.tsx";
import {
@@ -123,7 +123,7 @@ function AppContent() {
{!isAuthenticated && !authLoading && !showVersionCheck && (
<div className="fixed inset-0 flex items-center justify-center z-[10000]">
<Homepage
<Dashboard
onSelectView={handleSelectView}
isAuthenticated={isAuthenticated}
authLoading={authLoading}
@@ -149,7 +149,7 @@ function AppContent() {
{showHome && (
<div className="h-screen w-full visible pointer-events-auto static overflow-hidden">
<Homepage
<Dashboard
onSelectView={handleSelectView}
isAuthenticated={isAuthenticated}
authLoading={authLoading}

View File

@@ -24,7 +24,7 @@ import {
} from "@/ui/main-axios.ts";
import { PasswordInput } from "@/components/ui/password-input.tsx";
interface HomepageAuthProps extends React.ComponentProps<"div"> {
interface AuthProps extends React.ComponentProps<"div"> {
setLoggedIn: (loggedIn: boolean) => void;
setIsAdmin: (isAdmin: boolean) => void;
setUsername: (username: string | null) => void;
@@ -40,7 +40,7 @@ interface HomepageAuthProps extends React.ComponentProps<"div"> {
}) => void;
}
export function HomepageAuth({
export function Auth({
className,
setLoggedIn,
setIsAdmin,
@@ -52,7 +52,7 @@ export function HomepageAuth({
setDbError,
onAuthSuccess,
...props
}: HomepageAuthProps) {
}: AuthProps) {
const { t } = useTranslation();
const [tab, setTab] = useState<"login" | "signup" | "external" | "reset">(
"login",

View File

@@ -8,7 +8,7 @@ import {
useTabs,
} from "@/ui/Mobile/Navigation/Tabs/TabContext.tsx";
import { getUserInfo } from "@/ui/main-axios.ts";
import { HomepageAuth } from "@/ui/Mobile/Homepage/HomepageAuth.tsx";
import { Auth } from "@/ui/Mobile/Authentication/Auth.tsx";
import { useTranslation } from "react-i18next";
import { Toaster } from "@/components/ui/sonner.tsx";
@@ -124,7 +124,7 @@ const AppContent: FC = () => {
if (!isAuthenticated) {
return (
<div className="h-screen w-screen flex items-center justify-center bg-dark-bg p-4">
<HomepageAuth
<Auth
setLoggedIn={setIsAuthenticated}
setIsAdmin={setIsAdmin}
setUsername={setUsername}