Initial UI commit for 1.3

This commit is contained in:
LukeGus
2025-08-14 01:24:05 -05:00
parent 96864dbeb4
commit 81d1db09e4
33 changed files with 196 additions and 272 deletions

2
.env
View File

@@ -1 +1 @@
VERSION=1.1.1 VERSION=1.3

View File

@@ -1,14 +1,64 @@
import React from "react" import React, { useState, useEffect } from "react"
import { Sidebar } from "@/ui/Navigation/Sidebar.tsx"
import { Homepage } from "@/ui/Homepage/Homepage.tsx"
import { Terminal } from "@/ui/SSH/Terminal/Terminal.tsx"
import { SSHTunnel } from "@/ui/SSH/Tunnel/SSHTunnel.tsx"
import { ConfigEditor } from "@/ui/SSH/Config Editor/ConfigEditor.tsx"
import { SSHManager } from "@/ui/SSH/Manager/SSHManager.tsx"
import axios from "axios"
import {Homepage} from "@/apps/Homepage/Homepage.tsx" const apiBase = import.meta.env.DEV ? "http://localhost:8081/users" : "/users";
import {Terminal} from "@/apps/SSH/Terminal/Terminal.tsx" const API = axios.create({ baseURL: apiBase });
import {SSHTunnel} from "@/apps/SSH/Tunnel/SSHTunnel.tsx";
import {ConfigEditor} from "@/apps/SSH/Config Editor/ConfigEditor.tsx"; function getCookie(name: string) {
import {SSHManager} from "@/apps/SSH/Manager/SSHManager.tsx" return document.cookie.split('; ').reduce((r, v) => {
const parts = v.split('=');
return parts[0] === name ? decodeURIComponent(parts[1]) : r;
}, "");
}
function App() { function App() {
const [view, setView] = React.useState<string>("homepage") const [view, setView] = React.useState<string>("homepage")
const [mountedViews, setMountedViews] = React.useState<Set<string>>(new Set(["homepage"])) const [mountedViews, setMountedViews] = React.useState<Set<string>>(new Set(["homepage"]))
const [isAuthenticated, setIsAuthenticated] = useState(false)
const [username, setUsername] = useState<string | null>(null)
const [isAdmin, setIsAdmin] = useState(false)
const [authLoading, setAuthLoading] = useState(true)
useEffect(() => {
const checkAuth = () => {
const jwt = getCookie("jwt");
if (jwt) {
setAuthLoading(true);
API.get("/me", {headers: {Authorization: `Bearer ${jwt}`}})
.then((meRes) => {
setIsAuthenticated(true);
setIsAdmin(!!meRes.data.is_admin);
setUsername(meRes.data.username || null);
})
.catch((err) => {
setIsAuthenticated(false);
setIsAdmin(false);
setUsername(null);
// Clear invalid JWT
document.cookie = 'jwt=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;';
})
.finally(() => setAuthLoading(false));
} else {
setIsAuthenticated(false);
setIsAdmin(false);
setUsername(null);
setAuthLoading(false);
}
}
checkAuth()
const handleStorageChange = () => checkAuth()
window.addEventListener('storage', handleStorageChange)
return () => window.removeEventListener('storage', handleStorageChange)
}, [])
const handleSelectView = (nextView: string) => { const handleSelectView = (nextView: string) => {
setMountedViews((prev) => { setMountedViews((prev) => {
@@ -21,35 +71,38 @@ function App() {
} }
return ( return (
<div className="flex min-h-svh w-full"> <Sidebar
<main className="flex-1 w-full"> onSelectView={handleSelectView}
{mountedViews.has("homepage") && ( disabled={!isAuthenticated || authLoading}
<div style={{display: view === "homepage" ? "block" : "none"}}> isAdmin={isAdmin}
<Homepage onSelectView={handleSelectView} /> username={username}
</div> >
)} {mountedViews.has("homepage") && (
{mountedViews.has("ssh_manager") && ( <div style={{display: view === "homepage" ? "block" : "none"}}>
<div style={{display: view === "ssh_manager" ? "block" : "none"}}> <Homepage onSelectView={handleSelectView} />
<SSHManager onSelectView={handleSelectView} /> </div>
</div> )}
)} {mountedViews.has("ssh_manager") && (
{mountedViews.has("terminal") && ( <div style={{display: view === "ssh_manager" ? "block" : "none"}}>
<div style={{display: view === "terminal" ? "block" : "none"}}> <SSHManager onSelectView={handleSelectView} />
<Terminal onSelectView={handleSelectView} /> </div>
</div> )}
)} {mountedViews.has("terminal") && (
{mountedViews.has("tunnel") && ( <div style={{display: view === "terminal" ? "block" : "none"}}>
<div style={{display: view === "tunnel" ? "block" : "none"}}> <Terminal onSelectView={handleSelectView} />
<SSHTunnel onSelectView={handleSelectView} /> </div>
</div> )}
)} {mountedViews.has("tunnel") && (
{mountedViews.has("config_editor") && ( <div style={{display: view === "tunnel" ? "block" : "none"}}>
<div style={{display: view === "config_editor" ? "block" : "none"}}> <SSHTunnel onSelectView={handleSelectView} />
<ConfigEditor onSelectView={handleSelectView} /> </div>
</div> )}
)} {mountedViews.has("config_editor") && (
</main> <div style={{display: view === "config_editor" ? "block" : "none"}}>
</div> <ConfigEditor onSelectView={handleSelectView} />
</div>
)}
</Sidebar>
) )
} }

View File

@@ -1,59 +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 SSHManagerSidebar({onSelectView}: SidebarProps): React.ReactElement {
return (
<SidebarProvider>
<Sidebar>
<SidebarContent>
<SidebarGroup>
<SidebarGroupLabel className="text-lg font-bold text-white flex items-center gap-2">
Termix / SSH Manager
</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

@@ -1,18 +0,0 @@
import React from "react";
import {TemplateSidebar} from "@/apps/Template/TemplateSidebar.tsx";
interface ConfigEditorProps {
onSelectView: (view: string) => void;
}
export function Template({onSelectView}: ConfigEditorProps): React.ReactElement {
return (
<div>
<TemplateSidebar
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 TemplateSidebar({onSelectView}: SidebarProps): React.ReactElement {
return (
<SidebarProvider>
<Sidebar>
<SidebarContent>
<SidebarGroup>
<SidebarGroupLabel className="text-lg font-bold text-white flex items-center gap-2">
Termix / Template
</SidebarGroupLabel>
<Separator className="p-0.25 mt-1 mb-1"/>
<SidebarGroupContent className="flex flex-col flex-grow">
<SidebarMenu>
<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

@@ -234,7 +234,7 @@ function Sidebar({
// Adjust the padding for floating and inset variants. // Adjust the padding for floating and inset variants.
variant === "floating" || variant === "inset" variant === "floating" || variant === "inset"
? "p-2 group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)+(--spacing(4))+2px)]" ? "p-2 group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)+(--spacing(4))+2px)]"
: "group-data-[collapsible=icon]:w-(--sidebar-width-icon) group-data-[side=left]:border-r group-data-[side=right]:border-l", : "group-data-[collapsible=icon]:w-(--sidebar-width-icon)",
className className
)} )}
{...props} {...props}
@@ -242,7 +242,7 @@ function Sidebar({
<div <div
data-sidebar="sidebar" data-sidebar="sidebar"
data-slot="sidebar-inner" data-slot="sidebar-inner"
className="bg-sidebar group-data-[variant=floating]:border-sidebar-border flex h-full w-full flex-col group-data-[variant=floating]:rounded-lg group-data-[variant=floating]:border group-data-[variant=floating]:shadow-sm" className="bg-sidebar flex h-full w-full flex-col group-data-[variant=floating]:rounded-lg group-data-[variant=floating]:border group-data-[variant=floating]:shadow-sm"
> >
{children} {children}
</div> </div>

View File

@@ -1,9 +1,8 @@
import {HomepageSidebar} from "@/apps/Homepage/HomepageSidebar.tsx";
import React, {useEffect, useState} from "react"; import React, {useEffect, useState} from "react";
import {HomepageAuth} from "@/apps/Homepage/HomepageAuth.tsx"; import {HomepageAuth} from "@/ui/Homepage/HomepageAuth.tsx";
import axios from "axios"; import axios from "axios";
import {HomepageUpdateLog} from "@/apps/Homepage/HompageUpdateLog.tsx"; import {HomepageUpdateLog} from "@/ui/Homepage/HompageUpdateLog.tsx";
import {AlertManager} from "@/apps/Homepage/AlertManager.tsx"; import {HomepageAlertManager} from "@/ui/Homepage/HomepageAlertManager.tsx";
interface HomepageProps { interface HomepageProps {
onSelectView: (view: string) => void; onSelectView: (view: string) => void;
@@ -70,35 +69,27 @@ export function Homepage({onSelectView}: HomepageProps): React.ReactElement {
}, []); }, []);
return ( return (
<HomepageSidebar <div className="w-full min-h-svh grid place-items-center">
onSelectView={onSelectView} <div className="flex flex-row items-center justify-center gap-8">
disabled={!loggedIn || authLoading} <HomepageAuth
isAdmin={isAdmin} setLoggedIn={setLoggedIn}
username={loggedIn ? username : null} setIsAdmin={setIsAdmin}
> setUsername={setUsername}
<div className="w-full min-h-svh grid place-items-center"> setUserId={setUserId}
<div className="flex flex-row items-center justify-center gap-8"> loggedIn={loggedIn}
<HomepageAuth authLoading={authLoading}
setLoggedIn={setLoggedIn} dbError={dbError}
setIsAdmin={setIsAdmin} setDbError={setDbError}
setUsername={setUsername} />
setUserId={setUserId} <HomepageUpdateLog
loggedIn={loggedIn}
authLoading={authLoading}
dbError={dbError}
setDbError={setDbError}
/>
<HomepageUpdateLog
loggedIn={loggedIn}
/>
</div>
{/* Alert Manager - replaces the old welcome card */}
<AlertManager
userId={userId}
loggedIn={loggedIn} loggedIn={loggedIn}
/> />
</div> </div>
</HomepageSidebar>
<HomepageAlertManager
userId={userId}
loggedIn={loggedIn}
/>
</div>
); );
} }

View File

@@ -1,7 +1,7 @@
import React from "react"; import React from "react";
import {Card, CardContent, CardFooter, CardHeader, CardTitle} from "@/components/ui/card"; import {Card, CardContent, CardFooter, CardHeader, CardTitle} from "@/components/ui/card.tsx";
import {Button} from "@/components/ui/button"; import {Button} from "@/components/ui/button.tsx";
import {Badge} from "@/components/ui/badge"; import {Badge} from "@/components/ui/badge.tsx";
import {X, ExternalLink, AlertTriangle, Info, CheckCircle, AlertCircle} from "lucide-react"; import {X, ExternalLink, AlertTriangle, Info, CheckCircle, AlertCircle} from "lucide-react";
interface TermixAlert { interface TermixAlert {
@@ -63,7 +63,7 @@ const getTypeBadgeVariant = (type?: string) => {
} }
}; };
export function AlertCard({alert, onDismiss, onClose}: AlertCardProps): React.ReactElement { export function HomepageAlertCard({alert, onDismiss, onClose}: AlertCardProps): React.ReactElement {
if (!alert) { if (!alert) {
return null; return null;
} }

View File

@@ -1,6 +1,6 @@
import React, {useEffect, useState} from "react"; import React, {useEffect, useState} from "react";
import {AlertCard} from "./AlertCard"; import {HomepageAlertCard} from "./HomepageAlertCard.tsx";
import {Button} from "@/components/ui/button"; import {Button} from "@/components/ui/button.tsx";
import axios from "axios"; import axios from "axios";
interface TermixAlert { interface TermixAlert {
@@ -25,7 +25,7 @@ const API = axios.create({
baseURL: apiBase, baseURL: apiBase,
}); });
export function AlertManager({userId, loggedIn}: AlertManagerProps): React.ReactElement { export function HomepageAlertManager({userId, loggedIn}: AlertManagerProps): React.ReactElement {
const [alerts, setAlerts] = useState<TermixAlert[]>([]); const [alerts, setAlerts] = useState<TermixAlert[]>([]);
const [currentAlertIndex, setCurrentAlertIndex] = useState(0); const [currentAlertIndex, setCurrentAlertIndex] = useState(0);
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
@@ -154,14 +154,12 @@ export function AlertManager({userId, loggedIn}: AlertManagerProps): React.React
return ( return (
<div className="fixed inset-0 flex items-center justify-center bg-background/80 backdrop-blur-sm z-10"> <div className="fixed inset-0 flex items-center justify-center bg-background/80 backdrop-blur-sm z-10">
<div className="relative w-full max-w-2xl mx-4"> <div className="relative w-full max-w-2xl mx-4">
{/* Current Alert */} <HomepageAlertCard
<AlertCard
alert={currentAlert} alert={currentAlert}
onDismiss={handleDismissAlert} onDismiss={handleDismissAlert}
onClose={handleCloseCurrentAlert} onClose={handleCloseCurrentAlert}
/> />
{/* Navigation Controls */}
{hasMultipleAlerts && ( {hasMultipleAlerts && (
<div className="absolute -bottom-16 left-1/2 transform -translate-x-1/2 flex items-center gap-2"> <div className="absolute -bottom-16 left-1/2 transform -translate-x-1/2 flex items-center gap-2">
<Button <Button
@@ -188,7 +186,6 @@ export function AlertManager({userId, loggedIn}: AlertManagerProps): React.React
</div> </div>
)} )}
{/* Error Display */}
{error && ( {error && (
<div className="absolute -bottom-20 left-1/2 transform -translate-x-1/2"> <div className="absolute -bottom-20 left-1/2 transform -translate-x-1/2">
<div className="bg-destructive text-destructive-foreground px-3 py-1 rounded text-sm"> <div className="bg-destructive text-destructive-foreground px-3 py-1 rounded text-sm">

View File

@@ -1,10 +1,9 @@
import React, {useState, useEffect} from "react"; import React, {useState, useEffect} from "react";
import {cn} from "@/lib/utils"; import {cn} from "@/lib/utils.ts";
import {Button} from "@/components/ui/button"; import {Button} from "@/components/ui/button.tsx";
import {Input} from "@/components/ui/input"; import {Input} from "@/components/ui/input.tsx";
import {Label} from "@/components/ui/label"; import {Label} from "@/components/ui/label.tsx";
import {Alert, AlertTitle, AlertDescription} from "@/components/ui/alert"; import {Alert, AlertTitle, AlertDescription} from "@/components/ui/alert.tsx";
import {Separator} from "@/components/ui/separator";
import axios from "axios"; import axios from "axios";
function setCookie(name: string, value: string, days = 7) { function setCookie(name: string, value: string, days = 7) {

View File

@@ -1,9 +1,9 @@
import React from 'react'; import React, {useState} from 'react';
import { import {
Computer, Computer,
Server, Server,
File, File,
Hammer, ChevronUp, User2, HardDrive, Trash2, Users, Shield, Settings Hammer, ChevronUp, User2, HardDrive, Trash2, Users, Shield, Settings, Menu, ChevronRight
} from "lucide-react"; } from "lucide-react";
import { import {
@@ -14,7 +14,7 @@ import {
SidebarGroupLabel, SidebarGroupLabel,
SidebarMenu, SidebarMenu,
SidebarMenuButton, SidebarMenuButton,
SidebarMenuItem, SidebarProvider, SidebarInset, SidebarMenuItem, SidebarProvider, SidebarInset, SidebarHeader,
} from "@/components/ui/sidebar.tsx" } from "@/components/ui/sidebar.tsx"
import { import {
@@ -74,7 +74,7 @@ const API = axios.create({
baseURL: apiBase, baseURL: apiBase,
}); });
export function HomepageSidebar({ export function Sidebar({
onSelectView, onSelectView,
getView, getView,
disabled, disabled,
@@ -117,6 +117,8 @@ export function HomepageSidebar({
const [makeAdminError, setMakeAdminError] = React.useState<string | null>(null); const [makeAdminError, setMakeAdminError] = React.useState<string | null>(null);
const [makeAdminSuccess, setMakeAdminSuccess] = React.useState<string | null>(null); const [makeAdminSuccess, setMakeAdminSuccess] = React.useState<string | null>(null);
const [isSidebarOpen, setIsSidebarOpen] = useState<boolean>(true);
React.useEffect(() => { React.useEffect(() => {
if (adminSheetOpen) { if (adminSheetOpen) {
const jwt = getCookie("jwt"); const jwt = getCookie("jwt");
@@ -341,14 +343,23 @@ export function HomepageSidebar({
return ( return (
<div className="min-h-svh"> <div className="min-h-svh">
<SidebarProvider> <SidebarProvider open={isSidebarOpen}>
<Sidebar> <Sidebar variant="floating">
<SidebarHeader>
<SidebarGroupLabel className="text-lg font-bold text-white">
Termix
<Button
variant="outline"
onClick={() => setIsSidebarOpen(!isSidebarOpen)}
className="w-[28px] h-[28px] absolute right-5"
>
<Menu className="h-4 w-4"/>
</Button>
</SidebarGroupLabel>
</SidebarHeader>
<Separator className="p-0.25"/>
<SidebarContent> <SidebarContent>
<SidebarGroup> <SidebarGroup>
<SidebarGroupLabel className="text-lg font-bold text-white flex items-center gap-2">
Termix
</SidebarGroupLabel>
<Separator className="p-0.25 mt-1 mb-1"/>
<SidebarGroupContent> <SidebarGroupContent>
<SidebarMenu> <SidebarMenu>
<SidebarMenuItem key={"SSH Manager"}> <SidebarMenuItem key={"SSH Manager"}>
@@ -893,6 +904,14 @@ export function HomepageSidebar({
{children} {children}
</SidebarInset> </SidebarInset>
</SidebarProvider> </SidebarProvider>
{!isSidebarOpen && (
<div
onClick={() => setIsSidebarOpen(true)}
className="absolute top-0 left-0 w-[10px] h-full bg-[#18181b] cursor-pointer z-20 flex items-center justify-center">
<ChevronRight size={10} />
</div>
)}
</div> </div>
) )
} }

View File

@@ -1,10 +1,10 @@
import React, {useState, useEffect, useRef} from "react"; import React, {useState, useEffect, useRef} from "react";
import {ConfigEditorSidebar} from "@/apps/SSH/Config Editor/ConfigEditorSidebar.tsx"; import {ConfigEditorSidebar} from "@/ui/SSH/Config Editor/ConfigEditorSidebar.tsx";
import {ConfigTabList} from "@/apps/SSH/Config Editor/ConfigTabList.tsx"; import {ConfigTabList} from "@/ui/SSH/Config Editor/ConfigTabList.tsx";
import {ConfigHomeView} from "@/apps/SSH/Config Editor/ConfigHomeView.tsx"; import {ConfigHomeView} from "@/ui/SSH/Config Editor/ConfigHomeView.tsx";
import {ConfigCodeEditor} from "@/apps/SSH/Config Editor/ConfigCodeEditor.tsx"; import {ConfigCodeEditor} from "@/ui/SSH/Config Editor/ConfigCodeEditor.tsx";
import {Button} from '@/components/ui/button.tsx'; import {Button} from '@/components/ui/button.tsx';
import {ConfigTopbar} from "@/apps/SSH/Config Editor/ConfigTopbar.tsx"; import {ConfigTopbar} from "@/ui/SSH/Config Editor/ConfigTopbar.tsx";
import {cn} from '@/lib/utils.ts'; import {cn} from '@/lib/utils.ts';
import { import {
getConfigEditorRecent, getConfigEditorRecent,
@@ -20,7 +20,7 @@ import {
writeSSHFile, writeSSHFile,
getSSHStatus, getSSHStatus,
connectSSH connectSSH
} from '@/apps/SSH/ssh-axios.ts'; } from '@/ui/SSH/ssh-axios.ts';
interface Tab { interface Tab {
id: string | number; id: string | number;

View File

@@ -22,7 +22,7 @@ import {
getConfigEditorPinned, getConfigEditorPinned,
addConfigEditorPinned, addConfigEditorPinned,
removeConfigEditorPinned removeConfigEditorPinned
} from '@/apps/SSH/ssh-axios.ts'; } from '@/ui/SSH/ssh-axios.ts';
interface SSHHost { interface SSHHost {
id: number; id: number;

View File

@@ -1,9 +1,9 @@
import React, {useState} from "react"; import React, {useState} from "react";
import {SSHManagerSidebar} from "@/apps/SSH/Manager/SSHManagerSidebar.tsx"; import {SSHManagerHostViewer} from "@/ui/SSH/Manager/SSHManagerHostViewer.tsx"
import {SSHManagerHostViewer} from "@/apps/SSH/Manager/SSHManagerHostViewer.tsx"
import {Tabs, TabsContent, TabsList, TabsTrigger} from "@/components/ui/tabs.tsx"; import {Tabs, TabsContent, TabsList, TabsTrigger} from "@/components/ui/tabs.tsx";
import {Separator} from "@/components/ui/separator.tsx"; import {Separator} from "@/components/ui/separator.tsx";
import {SSHManagerHostEditor} from "@/apps/SSH/Manager/SSHManagerHostEditor.tsx"; import {SSHManagerHostEditor} from "@/ui/SSH/Manager/SSHManagerHostEditor.tsx";
import {useSidebar} from "@/components/ui/sidebar.tsx";
interface ConfigEditorProps { interface ConfigEditorProps {
onSelectView: (view: string) => void; onSelectView: (view: string) => void;
@@ -35,6 +35,7 @@ interface SSHHost {
export function SSHManager({onSelectView}: ConfigEditorProps): React.ReactElement { export function SSHManager({onSelectView}: ConfigEditorProps): React.ReactElement {
const [activeTab, setActiveTab] = useState("host_viewer"); const [activeTab, setActiveTab] = useState("host_viewer");
const [editingHost, setEditingHost] = useState<SSHHost | null>(null); const [editingHost, setEditingHost] = useState<SSHHost | null>(null);
const {state: sidebarState} = useSidebar();
const handleEditHost = (host: SSHHost) => { const handleEditHost = (host: SSHHost) => {
setEditingHost(host); setEditingHost(host);
@@ -55,29 +56,25 @@ export function SSHManager({onSelectView}: ConfigEditorProps): React.ReactElemen
return ( return (
<div> <div>
<SSHManagerSidebar <div className="flex w-full h-screen overflow-hidden">
onSelectView={onSelectView}
/>
<div className="flex w-screen h-screen overflow-hidden">
<div className="w-[256px]"/>
<div <div
className="flex-1 bg-[#18181b] m-[35px] text-white p-4 rounded-md w-[1200px] border h-[calc(100vh-70px)] flex flex-col min-h-0"> className={`flex-1 bg-[#18181b] m-[8px] text-white p-4 pt-0 rounded-lg border border-[#303032] flex flex-col min-h-0 ${
sidebarState === 'collapsed' ? 'ml-6' : ''
}`}>
<Tabs value={activeTab} onValueChange={handleTabChange} <Tabs value={activeTab} onValueChange={handleTabChange}
className="flex-1 flex flex-col h-full min-h-0"> className="flex-1 flex flex-col h-full min-h-0">
<TabsList> <TabsList className="mt-1.5">
<TabsTrigger value="host_viewer">Host Viewer</TabsTrigger> <TabsTrigger value="host_viewer">Host Viewer</TabsTrigger>
<TabsTrigger value="add_host"> <TabsTrigger value="add_host">
{editingHost ? "Edit Host" : "Add Host"} {editingHost ? "Edit Host" : "Add Host"}
</TabsTrigger> </TabsTrigger>
</TabsList> </TabsList>
<TabsContent value="host_viewer" className="flex-1 flex flex-col h-full min-h-0"> <TabsContent value="host_viewer" className="flex-1 flex flex-col h-full min-h-0">
<Separator className="p-0.25 mt-1 mb-1"/> <Separator className="p-0.25 -mt-0.5 mb-1"/>
<SSHManagerHostViewer onEditHost={handleEditHost}/> <SSHManagerHostViewer onEditHost={handleEditHost}/>
</TabsContent> </TabsContent>
<TabsContent value="add_host" className="flex-1 flex flex-col h-full min-h-0"> <TabsContent value="add_host" className="flex-1 flex flex-col h-full min-h-0">
<Separator className="p-0.25 mt-1 mb-1"/> <Separator className="p-0.25 -mt-0.5 mb-1"/>
<div className="flex flex-col h-full min-h-0"> <div className="flex flex-col h-full min-h-0">
<SSHManagerHostEditor <SSHManagerHostEditor
editingHost={editingHost} editingHost={editingHost}

View File

@@ -19,7 +19,7 @@ import {Tabs, TabsContent, TabsList, TabsTrigger} from "@/components/ui/tabs.tsx
import React, {useEffect, useRef, useState} from "react"; import React, {useEffect, useRef, useState} from "react";
import {Switch} from "@/components/ui/switch.tsx"; import {Switch} from "@/components/ui/switch.tsx";
import {Alert, AlertDescription} from "@/components/ui/alert.tsx"; import {Alert, AlertDescription} from "@/components/ui/alert.tsx";
import {createSSHHost, updateSSHHost, getSSHHosts} from '@/apps/SSH/ssh-axios'; import {createSSHHost, updateSSHHost, getSSHHosts} from '@/ui/SSH/ssh-axios';
interface SSHHost { interface SSHHost {
id: number; id: number;
@@ -388,7 +388,7 @@ export function SSHManagerHostEditor({editingHost, onFormSubmit}: SSHManagerHost
<div className="flex-1 flex flex-col h-full min-h-0 w-full"> <div className="flex-1 flex flex-col h-full min-h-0 w-full">
<Form {...form}> <Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)} className="flex flex-col flex-1 min-h-0 h-full"> <form onSubmit={form.handleSubmit(onSubmit)} className="flex flex-col flex-1 min-h-0 h-full">
<ScrollArea className="flex-1 min-h-0 w-full my-1"> <ScrollArea className="flex-1 min-h-0 w-full my-1 pb-2">
<Tabs defaultValue="general" className="w-full"> <Tabs defaultValue="general" className="w-full">
<TabsList> <TabsList>
<TabsTrigger value="general">General</TabsTrigger> <TabsTrigger value="general">General</TabsTrigger>
@@ -396,7 +396,7 @@ export function SSHManagerHostEditor({editingHost, onFormSubmit}: SSHManagerHost
<TabsTrigger value="tunnel">Tunnel</TabsTrigger> <TabsTrigger value="tunnel">Tunnel</TabsTrigger>
<TabsTrigger value="config_editor">Config Editor</TabsTrigger> <TabsTrigger value="config_editor">Config Editor</TabsTrigger>
</TabsList> </TabsList>
<TabsContent value="general"> <TabsContent value="general" className="pt-2">
<FormLabel className="mb-3 font-bold">Connection Details</FormLabel> <FormLabel className="mb-3 font-bold">Connection Details</FormLabel>
<div className="grid grid-cols-12 gap-4"> <div className="grid grid-cols-12 gap-4">
<FormField <FormField
@@ -1025,9 +1025,18 @@ export function SSHManagerHostEditor({editingHost, onFormSubmit}: SSHManagerHost
</TabsContent> </TabsContent>
</Tabs> </Tabs>
</ScrollArea> </ScrollArea>
<footer className="shrink-0 w-full"> <footer className="shrink-0 w-full pb-0">
<Separator className="p-0.25 mt-1 mb-3"/> <Separator className="p-0.25"/>
<Button type="submit" variant="outline">{editingHost ? "Update Host" : "Add Host"}</Button> <Button
className=""
type="submit"
variant="outline"
style={{
transform: 'translateY(8px)'
}}
>
{editingHost ? "Update Host" : "Add Host"}
</Button>
</footer> </footer>
</form> </form>
</Form> </Form>

View File

@@ -6,7 +6,7 @@ import {ScrollArea} from "@/components/ui/scroll-area";
import {Input} from "@/components/ui/input"; import {Input} from "@/components/ui/input";
import {Accordion, AccordionContent, AccordionItem, AccordionTrigger} from "@/components/ui/accordion"; import {Accordion, AccordionContent, AccordionItem, AccordionTrigger} from "@/components/ui/accordion";
import {Tooltip, TooltipContent, TooltipProvider, TooltipTrigger} from "@/components/ui/tooltip"; import {Tooltip, TooltipContent, TooltipProvider, TooltipTrigger} from "@/components/ui/tooltip";
import {getSSHHosts, deleteSSHHost, bulkImportSSHHosts} from "@/apps/SSH/ssh-axios"; import {getSSHHosts, deleteSSHHost, bulkImportSSHHosts} from "@/ui/SSH/ssh-axios";
import { import {
Edit, Edit,
Trash2, Trash2,

View File

@@ -1,7 +1,7 @@
import React, {useState, useRef, useEffect} from "react"; import React, {useState, useRef, useEffect} from "react";
import {TerminalSidebar} from "@/apps/SSH/Terminal/TerminalSidebar.tsx"; import {TerminalSidebar} from "@/ui/SSH/Terminal/TerminalSidebar.tsx";
import {TerminalComponent} from "./TerminalComponent.tsx"; import {TerminalComponent} from "./TerminalComponent.tsx";
import {TerminalTopbar} from "@/apps/SSH/Terminal/TerminalTopbar.tsx"; import {TerminalTopbar} from "@/ui/SSH/Terminal/TerminalTopbar.tsx";
import {ResizablePanelGroup, ResizablePanel, ResizableHandle} from '@/components/ui/resizable.tsx'; import {ResizablePanelGroup, ResizablePanel, ResizableHandle} from '@/components/ui/resizable.tsx';
import * as ResizablePrimitive from "react-resizable-panels"; import * as ResizablePrimitive from "react-resizable-panels";
import {ChevronDown, ChevronRight} from "lucide-react"; import {ChevronDown, ChevronRight} from "lucide-react";

View File

@@ -37,7 +37,7 @@ import {
} from "@/components/ui/accordion.tsx"; } from "@/components/ui/accordion.tsx";
import {ScrollArea} from "@/components/ui/scroll-area.tsx"; import {ScrollArea} from "@/components/ui/scroll-area.tsx";
import {Input} from "@/components/ui/input.tsx"; import {Input} from "@/components/ui/input.tsx";
import {getSSHHosts} from "@/apps/SSH/ssh-axios"; import {getSSHHosts} from "@/ui/SSH/ssh-axios";
import {Checkbox} from "@/components/ui/checkbox.tsx"; import {Checkbox} from "@/components/ui/checkbox.tsx";
interface SSHHost { interface SSHHost {

View File

@@ -1,4 +1,4 @@
import {TerminalTabList} from "@/apps/SSH/Terminal/TerminalTabList.tsx"; import {TerminalTabList} from "@/ui/SSH/Terminal/TerminalTabList.tsx";
import React from "react"; import React from "react";
import {ChevronUp} from "lucide-react"; import {ChevronUp} from "lucide-react";

View File

@@ -1,7 +1,7 @@
import React, {useState, useEffect, useCallback} from "react"; import React, {useState, useEffect, useCallback} from "react";
import {SSHTunnelSidebar} from "@/apps/SSH/Tunnel/SSHTunnelSidebar.tsx"; import {SSHTunnelSidebar} from "@/ui/SSH/Tunnel/SSHTunnelSidebar.tsx";
import {SSHTunnelViewer} from "@/apps/SSH/Tunnel/SSHTunnelViewer.tsx"; import {SSHTunnelViewer} from "@/ui/SSH/Tunnel/SSHTunnelViewer.tsx";
import {getSSHHosts, getTunnelStatuses, connectTunnel, disconnectTunnel, cancelTunnel} from "@/apps/SSH/ssh-axios"; import {getSSHHosts, getTunnelStatuses, connectTunnel, disconnectTunnel, cancelTunnel} from "@/ui/SSH/ssh-axios";
interface ConfigEditorProps { interface ConfigEditorProps {
onSelectView: (view: string) => void; onSelectView: (view: string) => void;

View File

@@ -6,16 +6,12 @@
"lib": ["ES2022", "DOM", "DOM.Iterable"], "lib": ["ES2022", "DOM", "DOM.Iterable"],
"module": "ESNext", "module": "ESNext",
"skipLibCheck": true, "skipLibCheck": true,
/* Bundler mode */
"moduleResolution": "bundler", "moduleResolution": "bundler",
"allowImportingTsExtensions": true, "allowImportingTsExtensions": true,
"verbatimModuleSyntax": true, "verbatimModuleSyntax": true,
"moduleDetection": "force", "moduleDetection": "force",
"noEmit": true, "noEmit": true,
"jsx": "react-jsx", "jsx": "react-jsx",
/* Linting - Made extremely permissive */
"strict": false, "strict": false,
"noUnusedLocals": false, "noUnusedLocals": false,
"noUnusedParameters": false, "noUnusedParameters": false,
@@ -31,8 +27,6 @@
"allowUnreachableCode": true, "allowUnreachableCode": true,
"noImplicitOverride": false, "noImplicitOverride": false,
"noEmitOnError": false, "noEmitOnError": false,
/* shadcn */
"baseUrl": ".", "baseUrl": ".",
"paths": { "paths": {
"@/*": [ "@/*": [