Update read me, license, tools, and auth loading
This commit is contained in:
13
LICENSE
Normal file
13
LICENSE
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
Copyright 2025 Luke Gustafson
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
55
README.md
55
README.md
@@ -5,58 +5,51 @@
|
|||||||
<a href="https://discord.gg/jVQGdvHDrf"><img alt="Discord" src="https://img.shields.io/discord/1347374268253470720"></a>
|
<a href="https://discord.gg/jVQGdvHDrf"><img alt="Discord" src="https://img.shields.io/discord/1347374268253470720"></a>
|
||||||
#### Top Technologies
|
#### Top Technologies
|
||||||
[](#)
|
[](#)
|
||||||
[](#)
|
[](#)
|
||||||
[](#)
|
[](#)
|
||||||
[](#)
|
[](#)
|
||||||
[](#)
|
[](#)
|
||||||
[](#)
|
[](#)
|
||||||
[](#)
|
[](#)
|
||||||
[](#)
|
[](#)
|
||||||
|
|
||||||
|
|
||||||
<br />
|
<br />
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<a href="https://github.com/LukeGus/Termix">
|
<a href="https://github.com/LukeGus/Termix">
|
||||||
<img alt="Termix Banner" src=../../Termix/repo-images/TermixLogo.png style="width: 125px; height: auto;"> </a>
|
<img alt="Termix Banner" src=../../Termix/public/icon.svg style="width: 250px; height: auto;"> </a>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
If you would like, you can support the project here!\
|
If you would like, you can support the project here!\
|
||||||
[](https://paypal.me/LukeGustafson803)
|
[](https://github.com/sponsors/LukeGus)
|
||||||
|
|
||||||
# Overview
|
# Overview
|
||||||
Termix is an open-source forever free self-hosted Homepage (other protocols planned, see [Planned Features](#planned-features)) server management panel inspired by [Nexterm](https://github.com/gnmyt/Nexterm). Its purpose is to provide an all-in-one docker-hosted web solution to manage your servers in one easy place. I'm using this project to help me learn [React](https://github.com/facebook/react), [Vite](https://github.com/vitejs/vite-plugin-react), and [Docker](https://www.docker.com) but also because I could never settle on a server management software that I enjoyed to use.
|
Termix is an open-source, forever-free, self-hosted all-in-one server management platform. It provides a web-based solution for managing your servers and infrastructure through a single, intuitive interface. Termix offers SSH terminal access, SSH tunneling capabilities, and remote file configuration editing - with many more tools to come.
|
||||||
|
|
||||||
> [!WARNING]
|
|
||||||
> This app is in the VERY early stages of development. Expect bugs, data loss, and unexplainable issues! For that reason, I recommend you securely tunnel your connection to Termix through a VPN.
|
|
||||||
|
|
||||||
# Features
|
# Features
|
||||||
- Homepage
|
- **SSH Terminal Access** - Full-featured terminal with split-screen support (up to 4 panels) and tab system
|
||||||
- Split Screen (Up to 4) & Tab System
|
- **SSH Tunnel Management** - Create and manage SSH tunnels with automatic reconnection and health monitoring
|
||||||
- User Authentication
|
- **Remote Config Editor** - Edit files directly on remote servers with syntax highlighting and file management
|
||||||
- Save Hosts (and easily view, connect, and manage them)
|
- **SSH Host Manager** - Save, organize, and manage your SSH connections with tags and folders
|
||||||
- SSHTerminal Themes
|
- **User Authentication** - Secure user management with admin controls
|
||||||
|
- **Modern UI** - Clean, responsive interface built with React, Tailwind CSS, and the amazing Shadcn
|
||||||
|
- **Docker Support** - Easy deployment with Docker and Docker Compose
|
||||||
|
|
||||||
# Planned Features
|
# Planned Features
|
||||||
- VNC
|
- **Improved Admin Control** - Ability to manage admins, and give more fine-grained control over their permissions, share hosts, reset passwords, delete accounts, etc
|
||||||
- RDP
|
- **More auth types** - Add 2FA, OCID support, etc
|
||||||
- SFTP (build in file transfer)
|
- **Theming** - Modify themeing for all tools
|
||||||
- ChatGPT/Ollama Integration (for commands)
|
- **Improved SFTP Support** - Ability to manage files easier with the config editor by uploading, creating, and removing files
|
||||||
- Apps (like notes, AI, etc)
|
- **Improved Terminal Support** - Add more terminal protocols such as VNC and RDP (anyone who has experience in integrating RDP into a web-application similar to Apache Guacamole, please contact me by creating an issue)
|
||||||
- User Management (roles, permissions, etc.)
|
|
||||||
- Homepage Tunneling
|
|
||||||
- More Authentication Methods
|
|
||||||
- More Security Features (like 2FA, etc.)
|
|
||||||
|
|
||||||
# Installation
|
# Installation
|
||||||
Visit the Termix [Wiki](https://github.com/LukeGus/Termix/wiki) for information on how to install Termix. You can also use these links to go directly to guide. [Docker](https://github.com/LukeGus/Termix/wiki/Docker) or [Manual](https://github.com/LukeGus/Termix/wiki/Manual).
|
Visit the Termix [Docs](https://docs.termix.site/docs) for information on how to install Termix.
|
||||||
|
|
||||||
# Support
|
# Support
|
||||||
If you need help with Termix, you can join the [Discord](https://discord.gg/jVQGdvHDrf) server and visit the support channel. You can also open an issue or open a pull request on the [GitHub](https://github.com/LukeGus/Termix/issues) repo. If you would like to support me financially, you can on [Paypal](https://paypal.me/LukeGustafson803).
|
If you need help with Termix, you can join the [Discord](https://discord.gg/jVQGdvHDrf) server and visit the support channel. You can also open an issue or open a pull request on the [GitHub](https://github.com/LukeGus/Termix/issues) repo.
|
||||||
|
|
||||||
# Show-off
|
# Show-off
|
||||||
|
TBD
|
||||||

|
|
||||||

|
|
||||||
|
|
||||||
# License
|
# License
|
||||||
Distributed under the MIT license. See LICENSE for more information.
|
Distributed under the Apache License Version 2.0. See LICENSE for more information.
|
||||||
@@ -8,7 +8,7 @@ services:
|
|||||||
volumes:
|
volumes:
|
||||||
- termix-data:/app/data
|
- termix-data:/app/data
|
||||||
environment:
|
environment:
|
||||||
PORT: 8080
|
PORT: "8080"
|
||||||
|
|
||||||
volumes:
|
volumes:
|
||||||
termix-data:
|
termix-data:
|
||||||
|
|||||||
@@ -1,10 +1,9 @@
|
|||||||
import React, {useEffect} from "react"
|
import React from "react"
|
||||||
|
|
||||||
import {Homepage} from "@/apps/Homepage/Homepage.tsx"
|
import {Homepage} from "@/apps/Homepage/Homepage.tsx"
|
||||||
import {SSH} from "@/apps/SSH/Terminal/SSH.tsx"
|
import {SSH} from "@/apps/SSH/Terminal/SSH.tsx"
|
||||||
import {SSHTunnel} from "@/apps/SSH/Tunnel/SSHTunnel.tsx";
|
import {SSHTunnel} from "@/apps/SSH/Tunnel/SSHTunnel.tsx";
|
||||||
import {ConfigEditor} from "@/apps/SSH/Config Editor/ConfigEditor.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"
|
import {SSHManager} from "@/apps/SSH/Manager/SSHManager.tsx"
|
||||||
|
|
||||||
function App() {
|
function App() {
|
||||||
@@ -32,10 +31,6 @@ function App() {
|
|||||||
return <ConfigEditor
|
return <ConfigEditor
|
||||||
onSelectView={setView}
|
onSelectView={setView}
|
||||||
/>
|
/>
|
||||||
case "tools":
|
|
||||||
return <Tools
|
|
||||||
onSelectView={setView}
|
|
||||||
/>
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,21 +1,71 @@
|
|||||||
import {HomepageSidebar} from "@/apps/Homepage/HomepageSidebar.tsx";
|
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 "@/apps/Homepage/HomepageAuth.tsx";
|
||||||
|
import axios from "axios";
|
||||||
|
|
||||||
interface HomepageProps {
|
interface HomepageProps {
|
||||||
onSelectView: (view: string) => void;
|
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 {
|
export function Homepage({onSelectView}: HomepageProps): React.ReactElement {
|
||||||
const [loggedIn, setLoggedIn] = useState(false);
|
const [loggedIn, setLoggedIn] = useState(false);
|
||||||
const [isAdmin, setIsAdmin] = useState(false);
|
const [isAdmin, setIsAdmin] = useState(false);
|
||||||
const [username, setUsername] = useState<string | null>(null);
|
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 (
|
return (
|
||||||
<div className="flex min-h-screen">
|
<div className="flex min-h-screen">
|
||||||
<HomepageSidebar
|
<HomepageSidebar
|
||||||
onSelectView={onSelectView}
|
onSelectView={onSelectView}
|
||||||
disabled={!loggedIn}
|
disabled={!loggedIn || authLoading}
|
||||||
isAdmin={isAdmin}
|
isAdmin={isAdmin}
|
||||||
username={loggedIn ? username : null}
|
username={loggedIn ? username : null}
|
||||||
/>
|
/>
|
||||||
@@ -28,6 +78,10 @@ export function Homepage({onSelectView}: HomepageProps): React.ReactElement {
|
|||||||
setLoggedIn={setLoggedIn}
|
setLoggedIn={setLoggedIn}
|
||||||
setIsAdmin={setIsAdmin}
|
setIsAdmin={setIsAdmin}
|
||||||
setUsername={setUsername}
|
setUsername={setUsername}
|
||||||
|
loggedIn={loggedIn}
|
||||||
|
authLoading={authLoading}
|
||||||
|
dbError={dbError}
|
||||||
|
setDbError={setDbError}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -31,9 +31,23 @@ interface HomepageAuthProps extends React.ComponentProps<"div"> {
|
|||||||
setLoggedIn: (loggedIn: boolean) => void;
|
setLoggedIn: (loggedIn: boolean) => void;
|
||||||
setIsAdmin: (isAdmin: boolean) => void;
|
setIsAdmin: (isAdmin: boolean) => void;
|
||||||
setUsername: (username: string | null) => 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 [tab, setTab] = useState<"login" | "signup">("login");
|
||||||
const [localUsername, setLocalUsername] = useState("");
|
const [localUsername, setLocalUsername] = useState("");
|
||||||
const [password, setPassword] = useState("");
|
const [password, setPassword] = useState("");
|
||||||
@@ -41,8 +55,12 @@ export function HomepageAuth({className, setLoggedIn, setIsAdmin, setUsername, .
|
|||||||
const [error, setError] = useState<string | null>(null);
|
const [error, setError] = useState<string | null>(null);
|
||||||
const [internalLoggedIn, setInternalLoggedIn] = useState(false);
|
const [internalLoggedIn, setInternalLoggedIn] = useState(false);
|
||||||
const [firstUser, setFirstUser] = useState(false);
|
const [firstUser, setFirstUser] = useState(false);
|
||||||
const [dbError, setDbError] = useState<string | null>(null);
|
|
||||||
const [registrationAllowed, setRegistrationAllowed] = useState(true);
|
const [registrationAllowed, setRegistrationAllowed] = useState(true);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setInternalLoggedIn(loggedIn);
|
||||||
|
}, [loggedIn]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
API.get("/registration-allowed").then(res => {
|
API.get("/registration-allowed").then(res => {
|
||||||
setRegistrationAllowed(res.data.allowed);
|
setRegistrationAllowed(res.data.allowed);
|
||||||
@@ -61,43 +79,7 @@ export function HomepageAuth({className, setLoggedIn, setIsAdmin, setUsername, .
|
|||||||
}).catch(() => {
|
}).catch(() => {
|
||||||
setDbError("Could not connect to the database. Please try again later.");
|
setDbError("Could not connect to the database. Please try again later.");
|
||||||
});
|
});
|
||||||
}, []);
|
}, [setDbError]);
|
||||||
|
|
||||||
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]);
|
|
||||||
|
|
||||||
async function handleSubmit(e: React.FormEvent) {
|
async function handleSubmit(e: React.FormEvent) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
@@ -179,7 +161,7 @@ export function HomepageAuth({className, setLoggedIn, setIsAdmin, setUsername, .
|
|||||||
</AlertDescription>
|
</AlertDescription>
|
||||||
</Alert>
|
</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-1 justify-center items-center p-0 m-0">
|
||||||
<div className="flex flex-col items-center gap-4">
|
<div className="flex flex-col items-center gap-4">
|
||||||
<Alert className="my-2">
|
<Alert className="my-2">
|
||||||
@@ -227,7 +209,7 @@ export function HomepageAuth({className, setLoggedIn, setIsAdmin, setUsername, .
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{(!internalLoggedIn && (!loading || !getCookie("jwt"))) && (
|
{(!internalLoggedIn && (!authLoading || !getCookie("jwt"))) && (
|
||||||
<>
|
<>
|
||||||
<div className="flex gap-2 mb-6">
|
<div className="flex gap-2 mb-6">
|
||||||
<button
|
<button
|
||||||
|
|||||||
@@ -34,12 +34,6 @@ import {
|
|||||||
import {Checkbox} from "@/components/ui/checkbox.tsx";
|
import {Checkbox} from "@/components/ui/checkbox.tsx";
|
||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
import {Button} from "@/components/ui/button.tsx";
|
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 {
|
interface SidebarProps {
|
||||||
onSelectView: (view: string) => void;
|
onSelectView: (view: string) => void;
|
||||||
@@ -145,7 +139,7 @@ export function HomepageSidebar({
|
|||||||
</SidebarMenuItem>
|
</SidebarMenuItem>
|
||||||
</div>
|
</div>
|
||||||
<SidebarMenuItem key={"Tools"}>
|
<SidebarMenuItem key={"Tools"}>
|
||||||
<SidebarMenuButton onClick={() => onSelectView("tools")} disabled={disabled}>
|
<SidebarMenuButton onClick={() => window.open("https://dashix.dev", "_blank")} disabled={disabled}>
|
||||||
<Hammer/>
|
<Hammer/>
|
||||||
<span>Tools</span>
|
<span>Tools</span>
|
||||||
</SidebarMenuButton>
|
</SidebarMenuButton>
|
||||||
|
|||||||
@@ -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>
|
className="text-xs text-red-500 bg-red-500/10 rounded px-2 py-1 border border-red-500/20">{hostsError}</div>
|
||||||
</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">
|
<div className="flex-1 min-h-0">
|
||||||
<ScrollArea className="w-full h-full">
|
<ScrollArea className="w-full h-full">
|
||||||
<Accordion key={`host-accordion-${sortedFolders.length}`}
|
<Accordion key={`host-accordion-${sortedFolders.length}`}
|
||||||
|
|||||||
@@ -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>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -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>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -83,6 +83,40 @@ app.post('/ssh/config_editor/ssh/connect', (req, res) => {
|
|||||||
readyTimeout: 20000,
|
readyTimeout: 20000,
|
||||||
keepaliveInterval: 10000,
|
keepaliveInterval: 10000,
|
||||||
keepaliveCountMax: 3,
|
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()) {
|
if (sshKey && sshKey.trim()) {
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import cors from 'cors';
|
|||||||
const app = express();
|
const app = express();
|
||||||
app.use(cors({
|
app.use(cors({
|
||||||
origin: '*',
|
origin: '*',
|
||||||
methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],
|
methods: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS'],
|
||||||
allowedHeaders: ['Content-Type', 'Authorization']
|
allowedHeaders: ['Content-Type', 'Authorization']
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user