Initial UI commit for 1.3
This commit is contained in:
123
src/App.tsx
123
src/App.tsx
@@ -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"
|
||||
import {Terminal} from "@/apps/SSH/Terminal/Terminal.tsx"
|
||||
import {SSHTunnel} from "@/apps/SSH/Tunnel/SSHTunnel.tsx";
|
||||
import {ConfigEditor} from "@/apps/SSH/Config Editor/ConfigEditor.tsx";
|
||||
import {SSHManager} from "@/apps/SSH/Manager/SSHManager.tsx"
|
||||
const apiBase = import.meta.env.DEV ? "http://localhost:8081/users" : "/users";
|
||||
const API = axios.create({ baseURL: apiBase });
|
||||
|
||||
function getCookie(name: string) {
|
||||
return document.cookie.split('; ').reduce((r, v) => {
|
||||
const parts = v.split('=');
|
||||
return parts[0] === name ? decodeURIComponent(parts[1]) : r;
|
||||
}, "");
|
||||
}
|
||||
|
||||
function App() {
|
||||
const [view, setView] = React.useState<string>("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) => {
|
||||
setMountedViews((prev) => {
|
||||
@@ -21,35 +71,38 @@ function App() {
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex min-h-svh w-full">
|
||||
<main className="flex-1 w-full">
|
||||
{mountedViews.has("homepage") && (
|
||||
<div style={{display: view === "homepage" ? "block" : "none"}}>
|
||||
<Homepage onSelectView={handleSelectView} />
|
||||
</div>
|
||||
)}
|
||||
{mountedViews.has("ssh_manager") && (
|
||||
<div style={{display: view === "ssh_manager" ? "block" : "none"}}>
|
||||
<SSHManager onSelectView={handleSelectView} />
|
||||
</div>
|
||||
)}
|
||||
{mountedViews.has("terminal") && (
|
||||
<div style={{display: view === "terminal" ? "block" : "none"}}>
|
||||
<Terminal onSelectView={handleSelectView} />
|
||||
</div>
|
||||
)}
|
||||
{mountedViews.has("tunnel") && (
|
||||
<div style={{display: view === "tunnel" ? "block" : "none"}}>
|
||||
<SSHTunnel onSelectView={handleSelectView} />
|
||||
</div>
|
||||
)}
|
||||
{mountedViews.has("config_editor") && (
|
||||
<div style={{display: view === "config_editor" ? "block" : "none"}}>
|
||||
<ConfigEditor onSelectView={handleSelectView} />
|
||||
</div>
|
||||
)}
|
||||
</main>
|
||||
</div>
|
||||
<Sidebar
|
||||
onSelectView={handleSelectView}
|
||||
disabled={!isAuthenticated || authLoading}
|
||||
isAdmin={isAdmin}
|
||||
username={username}
|
||||
>
|
||||
{mountedViews.has("homepage") && (
|
||||
<div style={{display: view === "homepage" ? "block" : "none"}}>
|
||||
<Homepage onSelectView={handleSelectView} />
|
||||
</div>
|
||||
)}
|
||||
{mountedViews.has("ssh_manager") && (
|
||||
<div style={{display: view === "ssh_manager" ? "block" : "none"}}>
|
||||
<SSHManager onSelectView={handleSelectView} />
|
||||
</div>
|
||||
)}
|
||||
{mountedViews.has("terminal") && (
|
||||
<div style={{display: view === "terminal" ? "block" : "none"}}>
|
||||
<Terminal onSelectView={handleSelectView} />
|
||||
</div>
|
||||
)}
|
||||
{mountedViews.has("tunnel") && (
|
||||
<div style={{display: view === "tunnel" ? "block" : "none"}}>
|
||||
<SSHTunnel onSelectView={handleSelectView} />
|
||||
</div>
|
||||
)}
|
||||
{mountedViews.has("config_editor") && (
|
||||
<div style={{display: view === "config_editor" ? "block" : "none"}}>
|
||||
<ConfigEditor onSelectView={handleSelectView} />
|
||||
</div>
|
||||
)}
|
||||
</Sidebar>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
@@ -234,7 +234,7 @@ function Sidebar({
|
||||
// Adjust the padding for floating and inset variants.
|
||||
variant === "floating" || variant === "inset"
|
||||
? "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
|
||||
)}
|
||||
{...props}
|
||||
@@ -242,7 +242,7 @@ function Sidebar({
|
||||
<div
|
||||
data-sidebar="sidebar"
|
||||
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}
|
||||
</div>
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
import {HomepageSidebar} from "@/apps/Homepage/HomepageSidebar.tsx";
|
||||
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 {HomepageUpdateLog} from "@/apps/Homepage/HompageUpdateLog.tsx";
|
||||
import {AlertManager} from "@/apps/Homepage/AlertManager.tsx";
|
||||
import {HomepageUpdateLog} from "@/ui/Homepage/HompageUpdateLog.tsx";
|
||||
import {HomepageAlertManager} from "@/ui/Homepage/HomepageAlertManager.tsx";
|
||||
|
||||
interface HomepageProps {
|
||||
onSelectView: (view: string) => void;
|
||||
@@ -70,35 +69,27 @@ export function Homepage({onSelectView}: HomepageProps): React.ReactElement {
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<HomepageSidebar
|
||||
onSelectView={onSelectView}
|
||||
disabled={!loggedIn || authLoading}
|
||||
isAdmin={isAdmin}
|
||||
username={loggedIn ? username : null}
|
||||
>
|
||||
<div className="w-full min-h-svh grid place-items-center">
|
||||
<div className="flex flex-row items-center justify-center gap-8">
|
||||
<HomepageAuth
|
||||
setLoggedIn={setLoggedIn}
|
||||
setIsAdmin={setIsAdmin}
|
||||
setUsername={setUsername}
|
||||
setUserId={setUserId}
|
||||
loggedIn={loggedIn}
|
||||
authLoading={authLoading}
|
||||
dbError={dbError}
|
||||
setDbError={setDbError}
|
||||
/>
|
||||
<HomepageUpdateLog
|
||||
loggedIn={loggedIn}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Alert Manager - replaces the old welcome card */}
|
||||
<AlertManager
|
||||
userId={userId}
|
||||
<div className="w-full min-h-svh grid place-items-center">
|
||||
<div className="flex flex-row items-center justify-center gap-8">
|
||||
<HomepageAuth
|
||||
setLoggedIn={setLoggedIn}
|
||||
setIsAdmin={setIsAdmin}
|
||||
setUsername={setUsername}
|
||||
setUserId={setUserId}
|
||||
loggedIn={loggedIn}
|
||||
authLoading={authLoading}
|
||||
dbError={dbError}
|
||||
setDbError={setDbError}
|
||||
/>
|
||||
<HomepageUpdateLog
|
||||
loggedIn={loggedIn}
|
||||
/>
|
||||
</div>
|
||||
</HomepageSidebar>
|
||||
|
||||
<HomepageAlertManager
|
||||
userId={userId}
|
||||
loggedIn={loggedIn}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
import React from "react";
|
||||
import {Card, CardContent, CardFooter, CardHeader, CardTitle} from "@/components/ui/card";
|
||||
import {Button} from "@/components/ui/button";
|
||||
import {Badge} from "@/components/ui/badge";
|
||||
import {Card, CardContent, CardFooter, CardHeader, CardTitle} from "@/components/ui/card.tsx";
|
||||
import {Button} from "@/components/ui/button.tsx";
|
||||
import {Badge} from "@/components/ui/badge.tsx";
|
||||
import {X, ExternalLink, AlertTriangle, Info, CheckCircle, AlertCircle} from "lucide-react";
|
||||
|
||||
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) {
|
||||
return null;
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
import React, {useEffect, useState} from "react";
|
||||
import {AlertCard} from "./AlertCard";
|
||||
import {Button} from "@/components/ui/button";
|
||||
import {HomepageAlertCard} from "./HomepageAlertCard.tsx";
|
||||
import {Button} from "@/components/ui/button.tsx";
|
||||
import axios from "axios";
|
||||
|
||||
interface TermixAlert {
|
||||
@@ -25,7 +25,7 @@ const API = axios.create({
|
||||
baseURL: apiBase,
|
||||
});
|
||||
|
||||
export function AlertManager({userId, loggedIn}: AlertManagerProps): React.ReactElement {
|
||||
export function HomepageAlertManager({userId, loggedIn}: AlertManagerProps): React.ReactElement {
|
||||
const [alerts, setAlerts] = useState<TermixAlert[]>([]);
|
||||
const [currentAlertIndex, setCurrentAlertIndex] = useState(0);
|
||||
const [loading, setLoading] = useState(false);
|
||||
@@ -154,14 +154,12 @@ export function AlertManager({userId, loggedIn}: AlertManagerProps): React.React
|
||||
return (
|
||||
<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">
|
||||
{/* Current Alert */}
|
||||
<AlertCard
|
||||
<HomepageAlertCard
|
||||
alert={currentAlert}
|
||||
onDismiss={handleDismissAlert}
|
||||
onClose={handleCloseCurrentAlert}
|
||||
/>
|
||||
|
||||
{/* Navigation Controls */}
|
||||
{hasMultipleAlerts && (
|
||||
<div className="absolute -bottom-16 left-1/2 transform -translate-x-1/2 flex items-center gap-2">
|
||||
<Button
|
||||
@@ -188,7 +186,6 @@ export function AlertManager({userId, loggedIn}: AlertManagerProps): React.React
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Error Display */}
|
||||
{error && (
|
||||
<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">
|
||||
@@ -1,10 +1,9 @@
|
||||
import React, {useState, useEffect} from "react";
|
||||
import {cn} from "@/lib/utils";
|
||||
import {Button} from "@/components/ui/button";
|
||||
import {Input} from "@/components/ui/input";
|
||||
import {Label} from "@/components/ui/label";
|
||||
import {Alert, AlertTitle, AlertDescription} from "@/components/ui/alert";
|
||||
import {Separator} from "@/components/ui/separator";
|
||||
import {cn} from "@/lib/utils.ts";
|
||||
import {Button} from "@/components/ui/button.tsx";
|
||||
import {Input} from "@/components/ui/input.tsx";
|
||||
import {Label} from "@/components/ui/label.tsx";
|
||||
import {Alert, AlertTitle, AlertDescription} from "@/components/ui/alert.tsx";
|
||||
import axios from "axios";
|
||||
|
||||
function setCookie(name: string, value: string, days = 7) {
|
||||
@@ -1,9 +1,9 @@
|
||||
import React from 'react';
|
||||
import React, {useState} from 'react';
|
||||
import {
|
||||
Computer,
|
||||
Server,
|
||||
File,
|
||||
Hammer, ChevronUp, User2, HardDrive, Trash2, Users, Shield, Settings
|
||||
Hammer, ChevronUp, User2, HardDrive, Trash2, Users, Shield, Settings, Menu, ChevronRight
|
||||
} from "lucide-react";
|
||||
|
||||
import {
|
||||
@@ -14,7 +14,7 @@ import {
|
||||
SidebarGroupLabel,
|
||||
SidebarMenu,
|
||||
SidebarMenuButton,
|
||||
SidebarMenuItem, SidebarProvider, SidebarInset,
|
||||
SidebarMenuItem, SidebarProvider, SidebarInset, SidebarHeader,
|
||||
} from "@/components/ui/sidebar.tsx"
|
||||
|
||||
import {
|
||||
@@ -74,7 +74,7 @@ const API = axios.create({
|
||||
baseURL: apiBase,
|
||||
});
|
||||
|
||||
export function HomepageSidebar({
|
||||
export function Sidebar({
|
||||
onSelectView,
|
||||
getView,
|
||||
disabled,
|
||||
@@ -117,6 +117,8 @@ export function HomepageSidebar({
|
||||
const [makeAdminError, setMakeAdminError] = React.useState<string | null>(null);
|
||||
const [makeAdminSuccess, setMakeAdminSuccess] = React.useState<string | null>(null);
|
||||
|
||||
const [isSidebarOpen, setIsSidebarOpen] = useState<boolean>(true);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (adminSheetOpen) {
|
||||
const jwt = getCookie("jwt");
|
||||
@@ -341,14 +343,23 @@ export function HomepageSidebar({
|
||||
|
||||
return (
|
||||
<div className="min-h-svh">
|
||||
<SidebarProvider>
|
||||
<Sidebar>
|
||||
<SidebarProvider open={isSidebarOpen}>
|
||||
<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>
|
||||
<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>
|
||||
<SidebarMenu>
|
||||
<SidebarMenuItem key={"SSH Manager"}>
|
||||
@@ -893,6 +904,14 @@ export function HomepageSidebar({
|
||||
{children}
|
||||
</SidebarInset>
|
||||
</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>
|
||||
)
|
||||
}
|
||||
@@ -1,10 +1,10 @@
|
||||
import React, {useState, useEffect, useRef} from "react";
|
||||
import {ConfigEditorSidebar} from "@/apps/SSH/Config Editor/ConfigEditorSidebar.tsx";
|
||||
import {ConfigTabList} from "@/apps/SSH/Config Editor/ConfigTabList.tsx";
|
||||
import {ConfigHomeView} from "@/apps/SSH/Config Editor/ConfigHomeView.tsx";
|
||||
import {ConfigCodeEditor} from "@/apps/SSH/Config Editor/ConfigCodeEditor.tsx";
|
||||
import {ConfigEditorSidebar} from "@/ui/SSH/Config Editor/ConfigEditorSidebar.tsx";
|
||||
import {ConfigTabList} from "@/ui/SSH/Config Editor/ConfigTabList.tsx";
|
||||
import {ConfigHomeView} from "@/ui/SSH/Config Editor/ConfigHomeView.tsx";
|
||||
import {ConfigCodeEditor} from "@/ui/SSH/Config Editor/ConfigCodeEditor.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 {
|
||||
getConfigEditorRecent,
|
||||
@@ -20,7 +20,7 @@ import {
|
||||
writeSSHFile,
|
||||
getSSHStatus,
|
||||
connectSSH
|
||||
} from '@/apps/SSH/ssh-axios.ts';
|
||||
} from '@/ui/SSH/ssh-axios.ts';
|
||||
|
||||
interface Tab {
|
||||
id: string | number;
|
||||
@@ -22,7 +22,7 @@ import {
|
||||
getConfigEditorPinned,
|
||||
addConfigEditorPinned,
|
||||
removeConfigEditorPinned
|
||||
} from '@/apps/SSH/ssh-axios.ts';
|
||||
} from '@/ui/SSH/ssh-axios.ts';
|
||||
|
||||
interface SSHHost {
|
||||
id: number;
|
||||
@@ -1,9 +1,9 @@
|
||||
import React, {useState} from "react";
|
||||
import {SSHManagerSidebar} from "@/apps/SSH/Manager/SSHManagerSidebar.tsx";
|
||||
import {SSHManagerHostViewer} from "@/apps/SSH/Manager/SSHManagerHostViewer.tsx"
|
||||
import {SSHManagerHostViewer} from "@/ui/SSH/Manager/SSHManagerHostViewer.tsx"
|
||||
import {Tabs, TabsContent, TabsList, TabsTrigger} from "@/components/ui/tabs.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 {
|
||||
onSelectView: (view: string) => void;
|
||||
@@ -35,6 +35,7 @@ interface SSHHost {
|
||||
export function SSHManager({onSelectView}: ConfigEditorProps): React.ReactElement {
|
||||
const [activeTab, setActiveTab] = useState("host_viewer");
|
||||
const [editingHost, setEditingHost] = useState<SSHHost | null>(null);
|
||||
const {state: sidebarState} = useSidebar();
|
||||
|
||||
const handleEditHost = (host: SSHHost) => {
|
||||
setEditingHost(host);
|
||||
@@ -55,29 +56,25 @@ export function SSHManager({onSelectView}: ConfigEditorProps): React.ReactElemen
|
||||
|
||||
return (
|
||||
<div>
|
||||
<SSHManagerSidebar
|
||||
onSelectView={onSelectView}
|
||||
/>
|
||||
|
||||
<div className="flex w-screen h-screen overflow-hidden">
|
||||
<div className="w-[256px]"/>
|
||||
|
||||
<div className="flex w-full h-screen overflow-hidden">
|
||||
<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}
|
||||
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="add_host">
|
||||
{editingHost ? "Edit Host" : "Add Host"}
|
||||
</TabsTrigger>
|
||||
</TabsList>
|
||||
<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}/>
|
||||
</TabsContent>
|
||||
<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">
|
||||
<SSHManagerHostEditor
|
||||
editingHost={editingHost}
|
||||
@@ -19,7 +19,7 @@ import {Tabs, TabsContent, TabsList, TabsTrigger} from "@/components/ui/tabs.tsx
|
||||
import React, {useEffect, useRef, useState} from "react";
|
||||
import {Switch} from "@/components/ui/switch.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 {
|
||||
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">
|
||||
<Form {...form}>
|
||||
<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">
|
||||
<TabsList>
|
||||
<TabsTrigger value="general">General</TabsTrigger>
|
||||
@@ -396,7 +396,7 @@ export function SSHManagerHostEditor({editingHost, onFormSubmit}: SSHManagerHost
|
||||
<TabsTrigger value="tunnel">Tunnel</TabsTrigger>
|
||||
<TabsTrigger value="config_editor">Config Editor</TabsTrigger>
|
||||
</TabsList>
|
||||
<TabsContent value="general">
|
||||
<TabsContent value="general" className="pt-2">
|
||||
<FormLabel className="mb-3 font-bold">Connection Details</FormLabel>
|
||||
<div className="grid grid-cols-12 gap-4">
|
||||
<FormField
|
||||
@@ -1025,9 +1025,18 @@ export function SSHManagerHostEditor({editingHost, onFormSubmit}: SSHManagerHost
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
</ScrollArea>
|
||||
<footer className="shrink-0 w-full">
|
||||
<Separator className="p-0.25 mt-1 mb-3"/>
|
||||
<Button type="submit" variant="outline">{editingHost ? "Update Host" : "Add Host"}</Button>
|
||||
<footer className="shrink-0 w-full pb-0">
|
||||
<Separator className="p-0.25"/>
|
||||
<Button
|
||||
className=""
|
||||
type="submit"
|
||||
variant="outline"
|
||||
style={{
|
||||
transform: 'translateY(8px)'
|
||||
}}
|
||||
>
|
||||
{editingHost ? "Update Host" : "Add Host"}
|
||||
</Button>
|
||||
</footer>
|
||||
</form>
|
||||
</Form>
|
||||
@@ -6,7 +6,7 @@ import {ScrollArea} from "@/components/ui/scroll-area";
|
||||
import {Input} from "@/components/ui/input";
|
||||
import {Accordion, AccordionContent, AccordionItem, AccordionTrigger} from "@/components/ui/accordion";
|
||||
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 {
|
||||
Edit,
|
||||
Trash2,
|
||||
@@ -1,7 +1,7 @@
|
||||
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 {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 * as ResizablePrimitive from "react-resizable-panels";
|
||||
import {ChevronDown, ChevronRight} from "lucide-react";
|
||||
@@ -37,7 +37,7 @@ import {
|
||||
} from "@/components/ui/accordion.tsx";
|
||||
import {ScrollArea} from "@/components/ui/scroll-area.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";
|
||||
|
||||
interface SSHHost {
|
||||
@@ -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 {ChevronUp} from "lucide-react";
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import React, {useState, useEffect, useCallback} from "react";
|
||||
import {SSHTunnelSidebar} from "@/apps/SSH/Tunnel/SSHTunnelSidebar.tsx";
|
||||
import {SSHTunnelViewer} from "@/apps/SSH/Tunnel/SSHTunnelViewer.tsx";
|
||||
import {getSSHHosts, getTunnelStatuses, connectTunnel, disconnectTunnel, cancelTunnel} from "@/apps/SSH/ssh-axios";
|
||||
import {SSHTunnelSidebar} from "@/ui/SSH/Tunnel/SSHTunnelSidebar.tsx";
|
||||
import {SSHTunnelViewer} from "@/ui/SSH/Tunnel/SSHTunnelViewer.tsx";
|
||||
import {getSSHHosts, getTunnelStatuses, connectTunnel, disconnectTunnel, cancelTunnel} from "@/ui/SSH/ssh-axios";
|
||||
|
||||
interface ConfigEditorProps {
|
||||
onSelectView: (view: string) => void;
|
||||
@@ -6,16 +6,12 @@
|
||||
"lib": ["ES2022", "DOM", "DOM.Iterable"],
|
||||
"module": "ESNext",
|
||||
"skipLibCheck": true,
|
||||
|
||||
/* Bundler mode */
|
||||
"moduleResolution": "bundler",
|
||||
"allowImportingTsExtensions": true,
|
||||
"verbatimModuleSyntax": true,
|
||||
"moduleDetection": "force",
|
||||
"noEmit": true,
|
||||
"jsx": "react-jsx",
|
||||
|
||||
/* Linting - Made extremely permissive */
|
||||
"strict": false,
|
||||
"noUnusedLocals": false,
|
||||
"noUnusedParameters": false,
|
||||
@@ -31,8 +27,6 @@
|
||||
"allowUnreachableCode": true,
|
||||
"noImplicitOverride": false,
|
||||
"noEmitOnError": false,
|
||||
|
||||
/* shadcn */
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"@/*": [
|
||||
|
||||
Reference in New Issue
Block a user