Update read me, license, tools, and auth loading

This commit is contained in:
LukeGus
2025-07-28 17:06:30 -05:00
parent a92ed01129
commit c2ed2729be
12 changed files with 153 additions and 172 deletions

View File

@@ -1,10 +1,9 @@
import React, {useEffect} from "react"
import React from "react"
import {Homepage} from "@/apps/Homepage/Homepage.tsx"
import {SSH} from "@/apps/SSH/Terminal/SSH.tsx"
import {SSHTunnel} from "@/apps/SSH/Tunnel/SSHTunnel.tsx";
import {ConfigEditor} from "@/apps/SSH/Config Editor/ConfigEditor.tsx";
import {Tools} from "@/apps/Tools/Tools.tsx";
import {SSHManager} from "@/apps/SSH/Manager/SSHManager.tsx"
function App() {
@@ -32,10 +31,6 @@ function App() {
return <ConfigEditor
onSelectView={setView}
/>
case "tools":
return <Tools
onSelectView={setView}
/>
}
}

View File

@@ -1,21 +1,71 @@
import {HomepageSidebar} from "@/apps/Homepage/HomepageSidebar.tsx";
import React, {useEffect, useState} from "react";
import {HomepageAuth} from "@/apps/Homepage/HomepageAuth.tsx";
import axios from "axios";
interface HomepageProps {
onSelectView: (view: string) => void;
}
function getCookie(name: string) {
return document.cookie.split('; ').reduce((r, v) => {
const parts = v.split('=');
return parts[0] === name ? decodeURIComponent(parts[1]) : r;
}, "");
}
const apiBase =
typeof window !== "undefined" && window.location.hostname === "localhost"
? "http://localhost:8081/users"
: "/users";
const API = axios.create({
baseURL: apiBase,
});
export function Homepage({onSelectView}: HomepageProps): React.ReactElement {
const [loggedIn, setLoggedIn] = useState(false);
const [isAdmin, setIsAdmin] = useState(false);
const [username, setUsername] = useState<string | null>(null);
const [authLoading, setAuthLoading] = useState(true);
const [dbError, setDbError] = useState<string | null>(null);
useEffect(() => {
const jwt = getCookie("jwt");
if (jwt) {
setAuthLoading(true);
Promise.all([
API.get("/me", {headers: {Authorization: `Bearer ${jwt}`}}),
API.get("/db-health")
])
.then(([meRes]) => {
setLoggedIn(true);
setIsAdmin(!!meRes.data.is_admin);
setUsername(meRes.data.username || null);
setDbError(null);
})
.catch((err) => {
setLoggedIn(false);
setIsAdmin(false);
setUsername(null);
setCookie("jwt", "", -1);
if (err?.response?.data?.error?.includes("Database")) {
setDbError("Could not connect to the database. Please try again later.");
} else {
setDbError(null);
}
})
.finally(() => setAuthLoading(false));
} else {
setAuthLoading(false);
}
}, []);
return (
<div className="flex min-h-screen">
<HomepageSidebar
onSelectView={onSelectView}
disabled={!loggedIn}
disabled={!loggedIn || authLoading}
isAdmin={isAdmin}
username={loggedIn ? username : null}
/>
@@ -28,6 +78,10 @@ export function Homepage({onSelectView}: HomepageProps): React.ReactElement {
setLoggedIn={setLoggedIn}
setIsAdmin={setIsAdmin}
setUsername={setUsername}
loggedIn={loggedIn}
authLoading={authLoading}
dbError={dbError}
setDbError={setDbError}
/>
</div>
</div>

View File

@@ -31,9 +31,23 @@ interface HomepageAuthProps extends React.ComponentProps<"div"> {
setLoggedIn: (loggedIn: boolean) => void;
setIsAdmin: (isAdmin: boolean) => void;
setUsername: (username: string | null) => void;
loggedIn: boolean;
authLoading: boolean;
dbError: string | null;
setDbError: (error: string | null) => void;
}
export function HomepageAuth({className, setLoggedIn, setIsAdmin, setUsername, ...props}: HomepageAuthProps) {
export function HomepageAuth({
className,
setLoggedIn,
setIsAdmin,
setUsername,
loggedIn,
authLoading,
dbError,
setDbError,
...props
}: HomepageAuthProps) {
const [tab, setTab] = useState<"login" | "signup">("login");
const [localUsername, setLocalUsername] = useState("");
const [password, setPassword] = useState("");
@@ -41,8 +55,12 @@ export function HomepageAuth({className, setLoggedIn, setIsAdmin, setUsername, .
const [error, setError] = useState<string | null>(null);
const [internalLoggedIn, setInternalLoggedIn] = useState(false);
const [firstUser, setFirstUser] = useState(false);
const [dbError, setDbError] = useState<string | null>(null);
const [registrationAllowed, setRegistrationAllowed] = useState(true);
useEffect(() => {
setInternalLoggedIn(loggedIn);
}, [loggedIn]);
useEffect(() => {
API.get("/registration-allowed").then(res => {
setRegistrationAllowed(res.data.allowed);
@@ -61,43 +79,7 @@ export function HomepageAuth({className, setLoggedIn, setIsAdmin, setUsername, .
}).catch(() => {
setDbError("Could not connect to the database. Please try again later.");
});
}, []);
useEffect(() => {
const jwt = getCookie("jwt");
if (jwt) {
setLoading(true);
Promise.all([
API.get("/me", {headers: {Authorization: `Bearer ${jwt}`}}),
API.get("/db-health")
])
.then(([meRes]) => {
setInternalLoggedIn(true);
setLoggedIn(true);
setIsAdmin(!!meRes.data.is_admin);
setUsername(meRes.data.username || null);
setDbError(null);
})
.catch((err) => {
setInternalLoggedIn(false);
setLoggedIn(false);
setIsAdmin(false);
setUsername(null);
setCookie("jwt", "", -1);
if (err?.response?.data?.error?.includes("Database")) {
setDbError("Could not connect to the database. Please try again later.");
} else {
setDbError(null);
}
})
.finally(() => setLoading(false));
} else {
setInternalLoggedIn(false);
setLoggedIn(false);
setIsAdmin(false);
setUsername(null);
}
}, [setLoggedIn, setIsAdmin, setUsername]);
}, [setDbError]);
async function handleSubmit(e: React.FormEvent) {
e.preventDefault();
@@ -179,7 +161,7 @@ export function HomepageAuth({className, setLoggedIn, setIsAdmin, setUsername, .
</AlertDescription>
</Alert>
)}
{(internalLoggedIn || (loading && getCookie("jwt"))) && (
{(internalLoggedIn || (authLoading && getCookie("jwt"))) && (
<div className="flex flex-1 justify-center items-center p-0 m-0">
<div className="flex flex-col items-center gap-4">
<Alert className="my-2">
@@ -227,7 +209,7 @@ export function HomepageAuth({className, setLoggedIn, setIsAdmin, setUsername, .
</div>
</div>
)}
{(!internalLoggedIn && (!loading || !getCookie("jwt"))) && (
{(!internalLoggedIn && (!authLoading || !getCookie("jwt"))) && (
<>
<div className="flex gap-2 mb-6">
<button

View File

@@ -34,12 +34,6 @@ import {
import {Checkbox} from "@/components/ui/checkbox.tsx";
import axios from "axios";
import {Button} from "@/components/ui/button.tsx";
import {Homepage} from "@/apps/Homepage/Homepage.tsx";
import {SSHManager} from "@/apps/SSH/Manager/SSHManager.tsx";
import {SSH} from "@/apps/SSH/Terminal/SSH.tsx";
import {SSHTunnel} from "@/apps/SSH/Tunnel/SSHTunnel.tsx";
import {ConfigEditor} from "@/apps/SSH/Config Editor/ConfigEditor.tsx";
import {Tools} from "@/apps/Tools/Tools.tsx";
interface SidebarProps {
onSelectView: (view: string) => void;
@@ -145,7 +139,7 @@ export function HomepageSidebar({
</SidebarMenuItem>
</div>
<SidebarMenuItem key={"Tools"}>
<SidebarMenuButton onClick={() => onSelectView("tools")} disabled={disabled}>
<SidebarMenuButton onClick={() => window.open("https://dashix.dev", "_blank")} disabled={disabled}>
<Hammer/>
<span>Tools</span>
</SidebarMenuButton>

View File

@@ -232,14 +232,6 @@ export function SSHSidebar({onSelectView, onHostConnect, allTabs, runCommandOnTa
className="text-xs text-red-500 bg-red-500/10 rounded px-2 py-1 border border-red-500/20">{hostsError}</div>
</div>
)}
{!hostsLoading && !hostsError && hosts.length === 0 && (
<div className="px-2 py-1 mt-2">
<div
className="text-xs text-muted-foreground bg-muted/20 rounded px-2 py-1 border border-border/20">No
hosts found.
</div>
</div>
)}
<div className="flex-1 min-h-0">
<ScrollArea className="w-full h-full">
<Accordion key={`host-accordion-${sortedFolders.length}`}

View File

@@ -1,18 +0,0 @@
import React from "react";
import {ToolsSidebar} from "@/apps/Tools/ToolsSidebar.tsx";
interface ConfigEditorProps {
onSelectView: (view: string) => void;
}
export function Tools({ onSelectView }: ConfigEditorProps): React.ReactElement {
return (
<div>
<ToolsSidebar
onSelectView={onSelectView}
/>
Template
</div>
)
}

View File

@@ -1,58 +0,0 @@
import React from 'react';
import {
CornerDownLeft
} from "lucide-react"
import {
Button
} from "@/components/ui/button.tsx"
import {
Sidebar,
SidebarContent,
SidebarGroup,
SidebarGroupContent,
SidebarGroupLabel,
SidebarMenu,
SidebarMenuItem, SidebarProvider,
} from "@/components/ui/sidebar.tsx"
import {
Separator,
} from "@/components/ui/separator.tsx"
interface SidebarProps {
onSelectView: (view: string) => void;
}
export function ToolsSidebar({ onSelectView }: SidebarProps): React.ReactElement {
return (
<SidebarProvider>
<Sidebar>
<SidebarContent>
<SidebarGroup>
<SidebarGroupLabel className="text-lg font-bold text-white flex items-center gap-2">
Termix / Tools
</SidebarGroupLabel>
<Separator className="p-0.25 mt-1 mb-1" />
<SidebarGroupContent className="flex flex-col flex-grow">
<SidebarMenu>
{/* Sidebar Items */}
<SidebarMenuItem key={"Homepage"}>
<Button className="w-full mt-2 mb-2 h-8" onClick={() => onSelectView("homepage")} variant="outline">
<CornerDownLeft/>
Return
</Button>
<Separator className="p-0.25 mt-1 mb-1" />
</SidebarMenuItem>
</SidebarMenu>
</SidebarGroupContent>
</SidebarGroup>
</SidebarContent>
</Sidebar>
</SidebarProvider>
)
}

View File

@@ -83,6 +83,40 @@ app.post('/ssh/config_editor/ssh/connect', (req, res) => {
readyTimeout: 20000,
keepaliveInterval: 10000,
keepaliveCountMax: 3,
algorithms: {
kex: [
'diffie-hellman-group14-sha256',
'diffie-hellman-group14-sha1',
'diffie-hellman-group1-sha1',
'diffie-hellman-group-exchange-sha256',
'diffie-hellman-group-exchange-sha1',
'ecdh-sha2-nistp256',
'ecdh-sha2-nistp384',
'ecdh-sha2-nistp521'
],
cipher: [
'aes128-ctr',
'aes192-ctr',
'aes256-ctr',
'aes128-gcm@openssh.com',
'aes256-gcm@openssh.com',
'aes128-cbc',
'aes192-cbc',
'aes256-cbc',
'3des-cbc'
],
hmac: [
'hmac-sha2-256',
'hmac-sha2-512',
'hmac-sha1',
'hmac-md5'
],
compress: [
'none',
'zlib@openssh.com',
'zlib'
]
}
};
if (sshKey && sshKey.trim()) {

View File

@@ -8,7 +8,7 @@ import cors from 'cors';
const app = express();
app.use(cors({
origin: '*',
methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],
methods: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS'],
allowedHeaders: ['Content-Type', 'Authorization']
}));