fix: small qol fixes and began readme update
This commit is contained in:
@@ -52,6 +52,7 @@ export function DockerManager({
|
||||
React.useState<DockerValidation | null>(null);
|
||||
const [isValidating, setIsValidating] = React.useState(false);
|
||||
const [viewMode, setViewMode] = React.useState<"list" | "detail">("list");
|
||||
const [isLoadingContainers, setIsLoadingContainers] = React.useState(false);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (hostConfig?.id !== currentHostConfig?.id) {
|
||||
@@ -179,12 +180,17 @@ export function DockerManager({
|
||||
|
||||
const pollContainers = async () => {
|
||||
try {
|
||||
setIsLoadingContainers(true);
|
||||
const data = await listDockerContainers(sessionId, true);
|
||||
if (!cancelled) {
|
||||
setContainers(data);
|
||||
}
|
||||
} catch (error) {
|
||||
// Silently handle polling errors
|
||||
} finally {
|
||||
if (!cancelled) {
|
||||
setIsLoadingContainers(false);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -334,16 +340,27 @@ export function DockerManager({
|
||||
{viewMode === "list" ? (
|
||||
<div className="h-full px-4 py-4">
|
||||
{sessionId ? (
|
||||
<ContainerList
|
||||
containers={containers}
|
||||
sessionId={sessionId}
|
||||
onSelectContainer={(id) => {
|
||||
setSelectedContainer(id);
|
||||
setViewMode("detail");
|
||||
}}
|
||||
selectedContainerId={selectedContainer}
|
||||
onRefresh={refreshContainers}
|
||||
/>
|
||||
isLoadingContainers && containers.length === 0 ? (
|
||||
<div className="flex items-center justify-center h-full">
|
||||
<div className="text-center">
|
||||
<SimpleLoader size="lg" />
|
||||
<p className="text-muted-foreground mt-4">
|
||||
{t("docker.loadingContainers")}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<ContainerList
|
||||
containers={containers}
|
||||
sessionId={sessionId}
|
||||
onSelectContainer={(id) => {
|
||||
setSelectedContainer(id);
|
||||
setViewMode("detail");
|
||||
}}
|
||||
selectedContainerId={selectedContainer}
|
||||
onRefresh={refreshContainers}
|
||||
/>
|
||||
)
|
||||
) : (
|
||||
<div className="text-center py-8">
|
||||
<p className="text-muted-foreground">No session available</p>
|
||||
|
||||
@@ -123,7 +123,7 @@ export const Terminal = forwardRef<TerminalHandle, SSHTerminalProps>(
|
||||
const pingIntervalRef = useRef<NodeJS.Timeout | null>(null);
|
||||
const [isConnected, setIsConnected] = useState(false);
|
||||
const [isConnecting, setIsConnecting] = useState(false);
|
||||
const [isFitted, setIsFitted] = useState(true);
|
||||
const [isFitted, setIsFitted] = useState(false);
|
||||
const [, setConnectionError] = useState<string | null>(null);
|
||||
const [, setIsAuthenticated] = useState(false);
|
||||
const [totpRequired, setTotpRequired] = useState(false);
|
||||
@@ -714,6 +714,8 @@ export const Terminal = forwardRef<TerminalHandle, SSHTerminalProps>(
|
||||
sudoPromptShownRef.current = false;
|
||||
}, 3000);
|
||||
},
|
||||
t("common.confirm"),
|
||||
t("common.cancel"),
|
||||
);
|
||||
setTimeout(() => {
|
||||
sudoPromptShownRef.current = false;
|
||||
@@ -1133,7 +1135,10 @@ export const Terminal = forwardRef<TerminalHandle, SSHTerminalProps>(
|
||||
if (terminal.cols < 10 || terminal.rows < 3) {
|
||||
requestAnimationFrame(() => {
|
||||
fitAddonRef.current?.fit();
|
||||
setIsFitted(true);
|
||||
});
|
||||
} else {
|
||||
setIsFitted(true);
|
||||
}
|
||||
|
||||
const element = xtermRef.current;
|
||||
@@ -1479,6 +1484,7 @@ export const Terminal = forwardRef<TerminalHandle, SSHTerminalProps>(
|
||||
className="h-full w-full"
|
||||
style={{
|
||||
pointerEvents: isVisible ? "auto" : "none",
|
||||
visibility: isConnecting || !isFitted ? "hidden" : "visible",
|
||||
}}
|
||||
onClick={() => {
|
||||
if (terminal && !splitScreen) {
|
||||
|
||||
@@ -532,6 +532,184 @@ export function HostManagerViewer({ onEditHost }: SSHManagerHostViewerProps) {
|
||||
}
|
||||
};
|
||||
|
||||
const getSampleData = () => ({
|
||||
hosts: [
|
||||
{
|
||||
name: t("interface.webServerProduction"),
|
||||
ip: "192.168.1.100",
|
||||
port: 22,
|
||||
username: "admin",
|
||||
authType: "password",
|
||||
password: "your_secure_password_here",
|
||||
folder: t("interface.productionFolder"),
|
||||
tags: ["web", "production", "nginx"],
|
||||
pin: true,
|
||||
notes: "Main production web server running Nginx",
|
||||
enableTerminal: true,
|
||||
enableTunnel: false,
|
||||
enableFileManager: true,
|
||||
enableDocker: false,
|
||||
defaultPath: "/var/www",
|
||||
},
|
||||
{
|
||||
name: t("interface.databaseServer"),
|
||||
ip: "192.168.1.101",
|
||||
port: 22,
|
||||
username: "dbadmin",
|
||||
authType: "key",
|
||||
key: "-----BEGIN OPENSSH PRIVATE KEY-----\\nYour SSH private key content here\\n-----END OPENSSH PRIVATE KEY-----",
|
||||
keyPassword: "optional_key_passphrase",
|
||||
keyType: "ssh-ed25519",
|
||||
folder: t("interface.productionFolder"),
|
||||
tags: ["database", "production", "postgresql"],
|
||||
pin: false,
|
||||
notes: "PostgreSQL production database",
|
||||
enableTerminal: true,
|
||||
enableTunnel: true,
|
||||
enableFileManager: false,
|
||||
enableDocker: false,
|
||||
tunnelConnections: [
|
||||
{
|
||||
sourcePort: 5432,
|
||||
endpointPort: 5432,
|
||||
endpointHost: t("interface.webServerProduction"),
|
||||
maxRetries: 3,
|
||||
retryInterval: 10,
|
||||
autoStart: true,
|
||||
},
|
||||
],
|
||||
statsConfig: {
|
||||
enabledWidgets: ["cpu", "memory", "disk", "network", "uptime"],
|
||||
statusCheckEnabled: true,
|
||||
statusCheckInterval: 30,
|
||||
metricsEnabled: true,
|
||||
metricsInterval: 30,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: t("interface.developmentServer"),
|
||||
ip: "192.168.1.102",
|
||||
port: 2222,
|
||||
username: "developer",
|
||||
authType: "credential",
|
||||
credentialId: 1,
|
||||
overrideCredentialUsername: false,
|
||||
folder: t("interface.developmentFolder"),
|
||||
tags: ["dev", "testing"],
|
||||
pin: false,
|
||||
notes: "Development environment for testing",
|
||||
enableTerminal: true,
|
||||
enableTunnel: false,
|
||||
enableFileManager: true,
|
||||
enableDocker: true,
|
||||
defaultPath: "/home/developer",
|
||||
},
|
||||
{
|
||||
name: "Jump Host Server",
|
||||
ip: "10.0.0.50",
|
||||
port: 22,
|
||||
username: "sysadmin",
|
||||
authType: "password",
|
||||
password: "secure_password",
|
||||
folder: "Infrastructure",
|
||||
tags: ["bastion", "jump-host"],
|
||||
notes: "Jump host for accessing internal network",
|
||||
enableTerminal: true,
|
||||
enableTunnel: true,
|
||||
enableFileManager: true,
|
||||
enableDocker: false,
|
||||
jumpHosts: [
|
||||
{
|
||||
hostId: 1,
|
||||
},
|
||||
],
|
||||
quickActions: [
|
||||
{
|
||||
name: "System Update",
|
||||
snippetId: 5,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Server with SOCKS5 Proxy",
|
||||
ip: "10.10.10.100",
|
||||
port: 22,
|
||||
username: "proxyuser",
|
||||
authType: "password",
|
||||
password: "secure_password",
|
||||
folder: "Proxied Hosts",
|
||||
tags: ["proxy", "socks5"],
|
||||
notes: "Accessible through SOCKS5 proxy",
|
||||
enableTerminal: true,
|
||||
enableTunnel: false,
|
||||
enableFileManager: true,
|
||||
enableDocker: false,
|
||||
useSocks5: true,
|
||||
socks5Host: "proxy.example.com",
|
||||
socks5Port: 1080,
|
||||
socks5Username: "proxyauth",
|
||||
socks5Password: "proxypass",
|
||||
},
|
||||
{
|
||||
name: "Customized Terminal Server",
|
||||
ip: "192.168.1.150",
|
||||
port: 22,
|
||||
username: "devops",
|
||||
authType: "password",
|
||||
password: "terminal_password",
|
||||
folder: t("interface.developmentFolder"),
|
||||
tags: ["custom", "terminal"],
|
||||
notes: "Server with custom terminal configuration",
|
||||
enableTerminal: true,
|
||||
enableTunnel: false,
|
||||
enableFileManager: true,
|
||||
enableDocker: false,
|
||||
defaultPath: "/opt/apps",
|
||||
terminalConfig: {
|
||||
cursorBlink: true,
|
||||
cursorStyle: "bar",
|
||||
fontSize: 16,
|
||||
fontFamily: "jetbrainsMono",
|
||||
letterSpacing: 0.5,
|
||||
lineHeight: 1.2,
|
||||
theme: "monokai",
|
||||
scrollback: 50000,
|
||||
bellStyle: "visual",
|
||||
rightClickSelectsWord: true,
|
||||
fastScrollModifier: "ctrl",
|
||||
fastScrollSensitivity: 7,
|
||||
minimumContrastRatio: 4,
|
||||
backspaceMode: "normal",
|
||||
agentForwarding: true,
|
||||
environmentVariables: [
|
||||
{
|
||||
key: "NODE_ENV",
|
||||
value: "development",
|
||||
},
|
||||
],
|
||||
autoMosh: false,
|
||||
sudoPasswordAutoFill: true,
|
||||
sudoPassword: "sudo_password_here",
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
const handleDownloadSample = () => {
|
||||
const sampleData = getSampleData();
|
||||
const blob = new Blob([JSON.stringify(sampleData, null, 2)], {
|
||||
type: "application/json",
|
||||
});
|
||||
const url = URL.createObjectURL(blob);
|
||||
const a = document.createElement("a");
|
||||
a.href = url;
|
||||
a.download = "sample-ssh-hosts.json";
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
document.body.removeChild(a);
|
||||
URL.revokeObjectURL(url);
|
||||
};
|
||||
|
||||
const handleJsonImport = async (
|
||||
event: React.ChangeEvent<HTMLInputElement>,
|
||||
) => {
|
||||
@@ -706,84 +884,7 @@ export function HostManagerViewer({ onEditHost }: SSHManagerHostViewerProps) {
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={() => {
|
||||
const sampleData = {
|
||||
hosts: [
|
||||
{
|
||||
name: t("interface.webServerProduction"),
|
||||
ip: "192.168.1.100",
|
||||
port: 22,
|
||||
username: "admin",
|
||||
authType: "password",
|
||||
password: "your_secure_password_here",
|
||||
folder: t("interface.productionFolder"),
|
||||
tags: ["web", "production", "nginx"],
|
||||
pin: true,
|
||||
enableTerminal: true,
|
||||
enableTunnel: false,
|
||||
enableFileManager: true,
|
||||
defaultPath: "/var/www",
|
||||
},
|
||||
{
|
||||
name: t("interface.databaseServer"),
|
||||
ip: "192.168.1.101",
|
||||
port: 22,
|
||||
username: "dbadmin",
|
||||
authType: "key",
|
||||
key: "-----BEGIN OPENSSH PRIVATE KEY-----\nYour SSH private key content here\n-----END OPENSSH PRIVATE KEY-----",
|
||||
keyPassword: "optional_key_passphrase",
|
||||
keyType: "ssh-ed25519",
|
||||
folder: t("interface.productionFolder"),
|
||||
tags: ["database", "production", "postgresql"],
|
||||
pin: false,
|
||||
enableTerminal: true,
|
||||
enableTunnel: true,
|
||||
enableFileManager: false,
|
||||
tunnelConnections: [
|
||||
{
|
||||
sourcePort: 5432,
|
||||
endpointPort: 5432,
|
||||
endpointHost: t("interface.webServerProduction"),
|
||||
maxRetries: 3,
|
||||
retryInterval: 10,
|
||||
autoStart: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: t("interface.developmentServer"),
|
||||
ip: "192.168.1.102",
|
||||
port: 2222,
|
||||
username: "developer",
|
||||
authType: "credential",
|
||||
credentialId: 1,
|
||||
folder: t("interface.developmentFolder"),
|
||||
tags: ["dev", "testing"],
|
||||
pin: false,
|
||||
enableTerminal: true,
|
||||
enableTunnel: false,
|
||||
enableFileManager: true,
|
||||
defaultPath: "/home/developer",
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const blob = new Blob([JSON.stringify(sampleData, null, 2)], {
|
||||
type: "application/json",
|
||||
});
|
||||
const url = URL.createObjectURL(blob);
|
||||
const a = document.createElement("a");
|
||||
a.href = url;
|
||||
a.download = "sample-ssh-hosts.json";
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
document.body.removeChild(a);
|
||||
URL.revokeObjectURL(url);
|
||||
}}
|
||||
>
|
||||
<Button variant="outline" size="sm" onClick={handleDownloadSample}>
|
||||
{t("hosts.downloadSample")}
|
||||
</Button>
|
||||
|
||||
@@ -867,84 +968,7 @@ export function HostManagerViewer({ onEditHost }: SSHManagerHostViewerProps) {
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={() => {
|
||||
const sampleData = {
|
||||
hosts: [
|
||||
{
|
||||
name: t("interface.webServerProduction"),
|
||||
ip: "192.168.1.100",
|
||||
port: 22,
|
||||
username: "admin",
|
||||
authType: "password",
|
||||
password: "your_secure_password_here",
|
||||
folder: t("interface.productionFolder"),
|
||||
tags: ["web", "production", "nginx"],
|
||||
pin: true,
|
||||
enableTerminal: true,
|
||||
enableTunnel: false,
|
||||
enableFileManager: true,
|
||||
defaultPath: "/var/www",
|
||||
},
|
||||
{
|
||||
name: t("interface.databaseServer"),
|
||||
ip: "192.168.1.101",
|
||||
port: 22,
|
||||
username: "dbadmin",
|
||||
authType: "key",
|
||||
key: "-----BEGIN OPENSSH PRIVATE KEY-----\nYour SSH private key content here\n-----END OPENSSH PRIVATE KEY-----",
|
||||
keyPassword: "optional_key_passphrase",
|
||||
keyType: "ssh-ed25519",
|
||||
folder: t("interface.productionFolder"),
|
||||
tags: ["database", "production", "postgresql"],
|
||||
pin: false,
|
||||
enableTerminal: true,
|
||||
enableTunnel: true,
|
||||
enableFileManager: false,
|
||||
tunnelConnections: [
|
||||
{
|
||||
sourcePort: 5432,
|
||||
endpointPort: 5432,
|
||||
endpointHost: t("interface.webServerProduction"),
|
||||
maxRetries: 3,
|
||||
retryInterval: 10,
|
||||
autoStart: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: t("interface.developmentServer"),
|
||||
ip: "192.168.1.102",
|
||||
port: 2222,
|
||||
username: "developer",
|
||||
authType: "credential",
|
||||
credentialId: 1,
|
||||
folder: t("interface.developmentFolder"),
|
||||
tags: ["dev", "testing"],
|
||||
pin: false,
|
||||
enableTerminal: true,
|
||||
enableTunnel: false,
|
||||
enableFileManager: true,
|
||||
defaultPath: "/home/developer",
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const blob = new Blob([JSON.stringify(sampleData, null, 2)], {
|
||||
type: "application/json",
|
||||
});
|
||||
const url = URL.createObjectURL(blob);
|
||||
const a = document.createElement("a");
|
||||
a.href = url;
|
||||
a.download = "sample-ssh-hosts.json";
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
document.body.removeChild(a);
|
||||
URL.revokeObjectURL(url);
|
||||
}}
|
||||
>
|
||||
<Button variant="outline" size="sm" onClick={handleDownloadSample}>
|
||||
{t("hosts.downloadSample")}
|
||||
</Button>
|
||||
|
||||
|
||||
@@ -639,7 +639,7 @@ export function HostTerminalTab({ form, snippets, t }: HostTerminalTabProps) {
|
||||
control={form.control}
|
||||
name="terminalConfig.sudoPasswordAutoFill"
|
||||
render={({ field }) => (
|
||||
<FormItem className="flex flex-row items-center justify-between rounded-lg border p-3">
|
||||
<FormItem className="flex flex-row items-center justify-between rounded-lg border p-3 bg-elevated dark:bg-input/30">
|
||||
<div className="space-y-0.5">
|
||||
<FormLabel>{t("hosts.sudoPasswordAutoFill")}</FormLabel>
|
||||
<FormDescription>
|
||||
|
||||
Reference in New Issue
Block a user