Remove encrpytion, improve logging and merge interfaces.

This commit is contained in:
LukeGus
2025-09-09 00:06:17 -05:00
parent ed7f85a3f4
commit aa6947ad58
44 changed files with 2341 additions and 3387 deletions

View File

@@ -313,17 +313,30 @@ export function AdminSettings({isTopbarOpen = true}: AdminSettingsProps): React.
<div className="flex gap-2 pt-2">
<Button type="submit" className="flex-1"
disabled={oidcLoading}>{oidcLoading ? t('admin.saving') : t('admin.saveConfiguration')}</Button>
<Button type="button" variant="outline" onClick={() => setOidcConfig({
client_id: '',
client_secret: '',
issuer_url: '',
authorization_url: '',
token_url: '',
identifier_path: 'sub',
name_path: 'name',
scopes: 'openid email profile',
userinfo_url: ''
})}>{t('admin.reset')}</Button>
<Button type="button" variant="outline" onClick={async () => {
const emptyConfig = {
client_id: '',
client_secret: '',
issuer_url: '',
authorization_url: '',
token_url: '',
identifier_path: '',
name_path: '',
scopes: '',
userinfo_url: ''
};
setOidcConfig(emptyConfig);
setOidcError(null);
setOidcLoading(true);
try {
await updateOIDCConfig(emptyConfig);
toast.success(t('admin.oidcConfigurationDisabled'));
} catch (err: any) {
setOidcError(err?.response?.data?.error || t('admin.failedToDisableOidcConfig'));
} finally {
setOidcLoading(false);
}
}} disabled={oidcLoading}>{t('admin.reset')}</Button>
</div>
</form>
</div>

View File

@@ -19,34 +19,16 @@ import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"
import React, { useEffect, useRef, useState } from "react"
import { Alert, AlertDescription } from "@/components/ui/alert"
import { toast } from "sonner"
import { createCredential, updateCredential, getCredentials } from '@/ui/main-axios'
import { createCredential, updateCredential, getCredentials, getCredentialDetails } from '@/ui/main-axios'
import { useTranslation } from "react-i18next"
interface Credential {
id: number;
name: string;
description?: string;
folder?: string;
tags: string[];
authType: 'password' | 'key';
username: string;
keyType?: string;
usageCount: number;
lastUsed?: string;
createdAt: string;
updatedAt: string;
}
interface CredentialEditorProps {
editingCredential?: Credential | null;
onFormSubmit?: () => void;
}
import type { Credential, CredentialEditorProps } from '../../../types/index.js'
export function CredentialEditor({ editingCredential, onFormSubmit }: CredentialEditorProps) {
const { t } = useTranslation();
const [credentials, setCredentials] = useState<Credential[]>([]);
const [folders, setFolders] = useState<string[]>([]);
const [loading, setLoading] = useState(true);
const [fullCredentialDetails, setFullCredentialDetails] = useState<Credential | null>(null);
const [authTab, setAuthTab] = useState<'password' | 'key'>('password');
@@ -60,8 +42,8 @@ export function CredentialEditor({ editingCredential, onFormSubmit }: Credential
const uniqueFolders = [...new Set(
credentialsData
.filter(credential => credential.folder && credential.folder.trim() !== '')
.map(credential => credential.folder)
)].sort();
.map(credential => credential.folder!)
)].sort() as string[];
setFolders(uniqueFolders);
} catch (error) {
@@ -73,6 +55,24 @@ export function CredentialEditor({ editingCredential, onFormSubmit }: Credential
fetchData();
}, []);
useEffect(() => {
const fetchCredentialDetails = async () => {
if (editingCredential) {
try {
const fullDetails = await getCredentialDetails(editingCredential.id);
setFullCredentialDetails(fullDetails);
} catch (error) {
console.error('Failed to fetch credential details:', error);
toast.error(t('credentials.failedToFetchCredentialDetails'));
}
} else {
setFullCredentialDetails(null);
}
};
fetchCredentialDetails();
}, [editingCredential, t]);
const formSchema = z.object({
name: z.string().min(1),
description: z.string().optional(),
@@ -81,7 +81,7 @@ export function CredentialEditor({ editingCredential, onFormSubmit }: Credential
authType: z.enum(['password', 'key']),
username: z.string().min(1),
password: z.string().optional(),
key: z.instanceof(File).optional().nullable(),
key: z.any().optional().nullable(),
keyPassword: z.string().optional(),
keyType: z.enum([
'rsa',
@@ -127,24 +127,24 @@ export function CredentialEditor({ editingCredential, onFormSubmit }: Credential
});
useEffect(() => {
if (editingCredential) {
const defaultAuthType = editingCredential.key ? 'key' : 'password';
if (editingCredential && fullCredentialDetails) {
const defaultAuthType = fullCredentialDetails.authType;
setAuthTab(defaultAuthType);
form.reset({
name: editingCredential.name || "",
description: editingCredential.description || "",
folder: editingCredential.folder || "",
tags: editingCredential.tags || [],
name: fullCredentialDetails.name || "",
description: fullCredentialDetails.description || "",
folder: fullCredentialDetails.folder || "",
tags: fullCredentialDetails.tags || [],
authType: defaultAuthType as 'password' | 'key',
username: editingCredential.username || "",
password: "",
username: fullCredentialDetails.username || "",
password: fullCredentialDetails.password || "",
key: null,
keyPassword: "",
keyType: (editingCredential.keyType as any) || "rsa",
keyPassword: fullCredentialDetails.keyPassword || "",
keyType: (fullCredentialDetails.keyType as any) || "rsa",
});
} else {
} else if (!editingCredential) {
setAuthTab('password');
form.reset({
@@ -160,7 +160,7 @@ export function CredentialEditor({ editingCredential, onFormSubmit }: Credential
keyType: "rsa",
});
}
}, [editingCredential, form]);
}, [editingCredential, fullCredentialDetails, form]);
const onSubmit = async (data: any) => {
try {
@@ -170,11 +170,38 @@ export function CredentialEditor({ editingCredential, onFormSubmit }: Credential
formData.name = formData.username;
}
const submitData: any = {
name: formData.name,
description: formData.description,
folder: formData.folder,
tags: formData.tags,
authType: formData.authType,
username: formData.username,
keyType: formData.keyType
};
if (formData.password !== undefined) {
submitData.password = formData.password;
}
if (formData.key !== undefined) {
if (formData.key instanceof File) {
const keyContent = await formData.key.text();
submitData.key = keyContent;
} else {
submitData.key = formData.key;
}
}
if (formData.keyPassword !== undefined) {
submitData.keyPassword = formData.keyPassword;
}
if (editingCredential) {
await updateCredential(editingCredential.id, formData);
await updateCredential(editingCredential.id, submitData);
toast.success(t('credentials.credentialUpdatedSuccessfully', { name: formData.name }));
} else {
await createCredential(formData);
await createCredential(submitData);
toast.success(t('credentials.credentialAddedSuccessfully', { name: formData.name }));
}

View File

@@ -27,45 +27,11 @@ import {
import { getCredentialDetails, getCredentialHosts } from '@/ui/main-axios';
import { toast } from 'sonner';
import { useTranslation } from 'react-i18next';
interface Credential {
id: number;
name: string;
description?: string;
folder?: string;
tags: string[];
authType: 'password' | 'key';
username: string;
keyType?: string;
usageCount: number;
lastUsed?: string;
createdAt: string;
updatedAt: string;
}
interface CredentialWithSecrets extends Credential {
password?: string;
key?: string;
keyPassword?: string;
}
interface HostInfo {
id: number;
name?: string;
ip: string;
port: number;
createdAt: string;
}
interface CredentialViewerProps {
credential: Credential;
onClose: () => void;
onEdit: () => void;
}
import type { Credential, HostInfo, CredentialViewerProps } from '../../../types/index.js';
const CredentialViewer: React.FC<CredentialViewerProps> = ({ credential, onClose, onEdit }) => {
const { t } = useTranslation();
const [credentialDetails, setCredentialDetails] = useState<CredentialWithSecrets | null>(null);
const [credentialDetails, setCredentialDetails] = useState<Credential | null>(null);
const [hostsUsing, setHostsUsing] = useState<HostInfo[]>([]);
const [loading, setLoading] = useState(true);
const [showSensitive, setShowSensitive] = useState<Record<string, boolean>>({});

View File

@@ -21,25 +21,7 @@ import { toast } from 'sonner';
import { useTranslation } from 'react-i18next';
import {CredentialEditor} from './CredentialEditor';
import CredentialViewer from './CredentialViewer';
interface Credential {
id: number;
name: string;
description?: string;
folder?: string;
tags: string[];
authType: 'password' | 'key';
username: string;
keyType?: string;
usageCount: number;
lastUsed?: string;
createdAt: string;
updatedAt: string;
}
interface CredentialsManagerProps {
onEditCredential?: (credential: Credential) => void;
}
import type { Credential, CredentialsManagerProps } from '../../../types/index.js';
export function CredentialsManager({ onEditCredential }: CredentialsManagerProps) {
const { t } = useTranslation();
@@ -83,20 +65,16 @@ export function CredentialsManager({ onEditCredential }: CredentialsManagerProps
toast.success(t('credentials.credentialDeletedSuccessfully', { name: credentialName }));
await fetchCredentials();
window.dispatchEvent(new CustomEvent('credentials:changed'));
} catch (err) {
toast.error(t('credentials.failedToDeleteCredential'));
} catch (err: any) {
if (err.response?.data?.details) {
toast.error(`${err.response.data.error}\n${err.response.data.details}`);
} else {
toast.error(t('credentials.failedToDeleteCredential'));
}
}
}
};
const filteredAndSortedCredentials = useMemo(() => {
let filtered = credentials;

View File

@@ -26,41 +26,7 @@ import {
getSSHStatus,
connectSSH
} from '@/ui/main-axios.ts';
interface Tab {
id: string | number;
title: string;
fileName: string;
content: string;
isSSH?: boolean;
sshSessionId?: string;
filePath?: string;
loading?: boolean;
dirty?: boolean;
}
interface SSHHost {
id: number;
name: string;
ip: string;
port: number;
username: string;
folder: string;
tags: string[];
pin: boolean;
authType: string;
password?: string;
key?: string;
keyPassword?: string;
keyType?: string;
enableTerminal: boolean;
enableTunnel: boolean;
enableFileManager: boolean;
defaultPath: string;
tunnelConnections: any[];
createdAt: string;
updatedAt: string;
}
import type { SSHHost, Tab, FileManagerProps } from '../../../types/index.js';
export function FileManager({onSelectView, embedded = false, initialHost = null}: {
onSelectView?: (view: string) => void,
@@ -378,12 +344,16 @@ export function FileManager({onSelectView, embedded = false, initialHost = null}
if (!status.connected) {
const connectPromise = connectSSH(tab.sshSessionId, {
hostId: currentHost.id,
ip: currentHost.ip,
port: currentHost.port,
username: currentHost.username,
password: currentHost.password,
sshKey: currentHost.key,
keyPassword: currentHost.keyPassword
keyPassword: currentHost.keyPassword,
authType: currentHost.authType,
credentialId: currentHost.credentialId,
userId: currentHost.userId
});
const connectTimeoutPromise = new Promise((_, reject) =>
setTimeout(() => reject(new Error(t('fileManager.sshReconnectionTimeout'))), 15000)

View File

@@ -5,19 +5,7 @@ import {Tabs, TabsList, TabsTrigger, TabsContent} from '@/components/ui/tabs.tsx
import {Input} from '@/components/ui/input.tsx';
import {useState} from 'react';
import {useTranslation} from 'react-i18next';
interface FileItem {
name: string;
path: string;
isPinned?: boolean;
type: 'file' | 'directory';
sshSessionId?: string;
}
interface ShortcutItem {
name: string;
path: string;
}
import type { FileItem, ShortcutItem } from '../../../types/index.js';
interface FileManagerHomeViewProps {
recent: FileItem[];

View File

@@ -19,29 +19,7 @@ import {
getSSHStatus,
connectSSH
} from '@/ui/main-axios.ts';
interface SSHHost {
id: number;
name: string;
ip: string;
port: number;
username: string;
folder: string;
tags: string[];
pin: boolean;
authType: string;
password?: string;
key?: string;
keyPassword?: string;
keyType?: string;
enableTerminal: boolean;
enableTunnel: boolean;
enableFileManager: boolean;
defaultPath: string;
tunnelConnections: any[];
createdAt: string;
updatedAt: string;
}
import type { SSHHost, FileManagerLeftSidebarProps } from '../../../types/index.js';
const FileManagerLeftSidebar = forwardRef(function FileManagerSidebar(
{onSelectView, onOpenFile, tabs, host, onOperationComplete, onError, onSuccess, onPathChange, onDeleteItem}: {
@@ -133,12 +111,16 @@ const FileManagerLeftSidebar = forwardRef(function FileManagerSidebar(
}
const connectionConfig = {
hostId: server.id,
ip: server.ip,
port: server.port,
username: server.username,
password: server.password,
sshKey: server.key,
keyPassword: server.keyPassword,
authType: server.authType,
credentialId: server.credentialId,
userId: server.userId,
};
await connectSSH(sessionId, connectionConfig);

View File

@@ -17,14 +17,7 @@ import {
} from 'lucide-react';
import {cn} from '@/lib/utils.ts';
import {useTranslation} from 'react-i18next';
interface FileManagerOperationsProps {
currentPath: string;
sshSessionId: string | null;
onOperationComplete: () => void;
onError: (error: string) => void;
onSuccess: (message: string) => void;
}
import type { FileManagerOperationsProps } from '../../../types/index.js';
export function FileManagerOperations({
currentPath,

View File

@@ -13,6 +13,7 @@ import {
import { getFoldersWithStats, renameFolder } from '@/ui/main-axios';
import { toast } from 'sonner';
import { useTranslation } from 'react-i18next';
import type { FolderManagerProps } from '../../../types/index.js';
interface FolderStats {
name: string;
@@ -24,10 +25,6 @@ interface FolderStats {
}>;
}
interface FolderManagerProps {
onFolderChanged?: () => void;
}
export function FolderManager({ onFolderChanged }: FolderManagerProps) {
const { t } = useTranslation();
const [folders, setFolders] = useState<FolderStats[]>([]);

View File

@@ -7,34 +7,7 @@ import {CredentialsManager} from "@/ui/Desktop/Apps/Credentials/CredentialsManag
import {CredentialEditor} from "@/ui/Desktop/Apps/Credentials/CredentialEditor.tsx";
import {useSidebar} from "@/components/ui/sidebar.tsx";
import {useTranslation} from "react-i18next";
interface HostManagerProps {
onSelectView: (view: string) => void;
isTopbarOpen?: boolean;
}
interface SSHHost {
id: number;
name: string;
ip: string;
port: number;
username: string;
folder: string;
tags: string[];
pin: boolean;
authType: string;
password?: string;
key?: string;
keyPassword?: string;
keyType?: string;
enableTerminal: boolean;
enableTunnel: boolean;
enableFileManager: boolean;
defaultPath: string;
tunnelConnections: any[];
createdAt: string;
updatedAt: string;
}
import type { SSHHost, HostManagerProps } from '../../../types/index.js';
export function HostManager({onSelectView, isTopbarOpen}: HostManagerProps): React.ReactElement {
const {t} = useTranslation();

View File

@@ -106,7 +106,7 @@ export function HostManagerHostEditor({editingHost, onFormSubmit}: SSHManagerHos
authType: z.enum(['password', 'key', 'credential']),
credentialId: z.number().optional().nullable(),
password: z.string().optional(),
key: z.instanceof(File).optional().nullable(),
key: z.any().optional().nullable(),
keyPassword: z.string().optional(),
keyType: z.enum([
'auto',
@@ -205,7 +205,6 @@ export function HostManagerHostEditor({editingHost, onFormSubmit}: SSHManagerHos
useEffect(() => {
if (editingHost) {
const defaultAuthType = editingHost.credentialId ? 'credential' : (editingHost.key ? 'key' : 'password');
setAuthTab(defaultAuthType);
form.reset({
@@ -219,7 +218,7 @@ export function HostManagerHostEditor({editingHost, onFormSubmit}: SSHManagerHos
authType: defaultAuthType as 'password' | 'key' | 'credential',
credentialId: editingHost.credentialId || null,
password: editingHost.password || "",
key: editingHost.key ? new File([editingHost.key], "key.pem") : null,
key: null,
keyPassword: editingHost.keyPassword || "",
keyType: (editingHost.keyType as any) || "auto",
enableTerminal: editingHost.enableTerminal !== false,
@@ -230,7 +229,6 @@ export function HostManagerHostEditor({editingHost, onFormSubmit}: SSHManagerHos
});
} else {
setAuthTab('password');
form.reset({
name: "",
ip: "",
@@ -283,11 +281,52 @@ export function HostManagerHostEditor({editingHost, onFormSubmit}: SSHManagerHos
formData.name = `${formData.username}@${formData.ip}`;
}
const submitData: any = {
name: formData.name,
ip: formData.ip,
port: formData.port,
username: formData.username,
folder: formData.folder,
tags: formData.tags,
pin: formData.pin,
authType: formData.authType,
enableTerminal: formData.enableTerminal,
enableTunnel: formData.enableTunnel,
enableFileManager: formData.enableFileManager,
defaultPath: formData.defaultPath,
tunnelConnections: formData.tunnelConnections
};
if (formData.authType === 'credential') {
submitData.credentialId = formData.credentialId;
submitData.password = null;
submitData.key = null;
submitData.keyPassword = null;
submitData.keyType = null;
} else if (formData.authType === 'password') {
submitData.credentialId = null;
submitData.password = formData.password;
submitData.key = null;
submitData.keyPassword = null;
submitData.keyType = null;
} else if (formData.authType === 'key') {
submitData.credentialId = null;
submitData.password = null;
if (formData.key instanceof File) {
const keyContent = await formData.key.text();
submitData.key = keyContent;
} else {
submitData.key = formData.key;
}
submitData.keyPassword = formData.keyPassword;
submitData.keyType = formData.keyType;
}
if (editingHost) {
await updateSSHHost(editingHost.id, formData);
await updateSSHHost(editingHost.id, submitData);
toast.success(t('hosts.hostUpdatedSuccessfully', { name: formData.name }));
} else {
await createSSHHost(formData);
await createSSHHost(submitData);
toast.success(t('hosts.hostAddedSuccessfully', { name: formData.name }));
}

View File

@@ -27,29 +27,7 @@ import {
Pencil
} from "lucide-react";
import {Separator} from "@/components/ui/separator.tsx";
interface SSHHost {
id: number;
name: string;
ip: string;
port: number;
username: string;
folder: string;
tags: string[];
pin: boolean;
authType: string;
enableTerminal: boolean;
enableTunnel: boolean;
enableFileManager: boolean;
defaultPath: string;
tunnelConnections: any[];
createdAt: string;
updatedAt: string;
}
interface SSHManagerHostViewerProps {
onEditHost?: (host: SSHHost) => void;
}
import type { SSHHost, SSHManagerHostViewerProps } from '../../../types/index.js';
export function HostManagerHostViewer({onEditHost}: SSHManagerHostViewerProps) {
const {t} = useTranslation();

View File

@@ -1,52 +1,7 @@
import React, {useState, useEffect, useCallback} from "react";
import {TunnelViewer} from "@/ui/Desktop/Apps/Tunnel/TunnelViewer.tsx";
import {getSSHHosts, getTunnelStatuses, connectTunnel, disconnectTunnel, cancelTunnel} from "@/ui/main-axios.ts";
interface TunnelConnection {
sourcePort: number;
endpointPort: number;
endpointHost: string;
maxRetries: number;
retryInterval: number;
autoStart: boolean;
}
interface SSHHost {
id: number;
name: string;
ip: string;
port: number;
username: string;
folder: string;
tags: string[];
pin: boolean;
authType: string;
password?: string;
key?: string;
keyPassword?: string;
keyType?: string;
enableTerminal: boolean;
enableTunnel: boolean;
enableFileManager: boolean;
defaultPath: string;
tunnelConnections: TunnelConnection[];
createdAt: string;
updatedAt: string;
}
interface TunnelStatus {
status: string;
reason?: string;
errorType?: string;
retryCount?: number;
maxRetries?: number;
nextRetryIn?: number;
retryExhausted?: boolean;
}
interface SSHTunnelProps {
filterHostKey?: string;
}
import type { SSHHost, TunnelConnection, TunnelStatus, SSHTunnelProps } from '../../../types/index.js';
export function Tunnel({filterHostKey}: SSHTunnelProps): React.ReactElement {
const [allHosts, setAllHosts] = useState<SSHHost[]>([]);
@@ -163,6 +118,8 @@ export function Tunnel({filterHostKey}: SSHTunnelProps): React.ReactElement {
sourceSSHKey: host.authType === 'key' ? host.key : undefined,
sourceKeyPassword: host.authType === 'key' ? host.keyPassword : undefined,
sourceKeyType: host.authType === 'key' ? host.keyType : undefined,
sourceCredentialId: host.credentialId,
sourceUserId: host.userId,
endpointIP: endpointHost.ip,
endpointSSHPort: endpointHost.port,
endpointUsername: endpointHost.username,
@@ -171,6 +128,8 @@ export function Tunnel({filterHostKey}: SSHTunnelProps): React.ReactElement {
endpointSSHKey: endpointHost.authType === 'key' ? endpointHost.key : undefined,
endpointKeyPassword: endpointHost.authType === 'key' ? endpointHost.keyPassword : undefined,
endpointKeyType: endpointHost.authType === 'key' ? endpointHost.keyType : undefined,
endpointCredentialId: endpointHost.credentialId,
endpointUserId: endpointHost.userId,
sourcePort: tunnel.sourcePort,
endpointPort: tunnel.endpointPort,
maxRetries: tunnel.maxRetries,

View File

@@ -20,65 +20,7 @@ import {
X
} from "lucide-react";
import {Badge} from "@/components/ui/badge.tsx";
const CONNECTION_STATES = {
DISCONNECTED: "disconnected",
CONNECTING: "connecting",
CONNECTED: "connected",
VERIFYING: "verifying",
FAILED: "failed",
UNSTABLE: "unstable",
RETRYING: "retrying",
WAITING: "waiting",
DISCONNECTING: "disconnecting"
};
interface TunnelConnection {
sourcePort: number;
endpointPort: number;
endpointHost: string;
maxRetries: number;
retryInterval: number;
autoStart: boolean;
}
interface SSHHost {
id: number;
name: string;
ip: string;
port: number;
username: string;
folder: string;
tags: string[];
pin: boolean;
authType: string;
enableTerminal: boolean;
enableTunnel: boolean;
enableFileManager: boolean;
defaultPath: string;
tunnelConnections: TunnelConnection[];
createdAt: string;
updatedAt: string;
}
interface TunnelStatus {
status: string;
reason?: string;
errorType?: string;
retryCount?: number;
maxRetries?: number;
nextRetryIn?: number;
retryExhausted?: boolean;
}
interface SSHTunnelObjectProps {
host: SSHHost;
tunnelStatuses: Record<string, TunnelStatus>;
tunnelActions: Record<string, boolean>;
onTunnelAction: (action: 'connect' | 'disconnect' | 'cancel', host: SSHHost, tunnelIndex: number) => Promise<any>;
compact?: boolean;
bare?: boolean;
}
import type { SSHHost, TunnelConnection, TunnelStatus, CONNECTION_STATES, SSHTunnelObjectProps } from '../../../types/index.js';
export function TunnelObject({
host,

View File

@@ -1,44 +1,7 @@
import React from "react";
import {TunnelObject} from "./TunnelObject.tsx";
import {useTranslation} from 'react-i18next';
interface TunnelConnection {
sourcePort: number;
endpointPort: number;
endpointHost: string;
maxRetries: number;
retryInterval: number;
autoStart: boolean;
}
interface SSHHost {
id: number;
name: string;
ip: string;
port: number;
username: string;
folder: string;
tags: string[];
pin: boolean;
authType: string;
enableTerminal: boolean;
enableTunnel: boolean;
enableFileManager: boolean;
defaultPath: string;
tunnelConnections: TunnelConnection[];
createdAt: string;
updatedAt: string;
}
interface TunnelStatus {
status: string;
reason?: string;
errorType?: string;
retryCount?: number;
maxRetries?: number;
nextRetryIn?: number;
retryExhausted?: boolean;
}
import type { SSHHost, TunnelConnection, TunnelStatus } from '../../../types/index.js';
interface SSHTunnelViewerProps {
hosts: SSHHost[];

View File

@@ -4,17 +4,7 @@ import {Button} from "@/components/ui/button.tsx";
import {Badge} from "@/components/ui/badge.tsx";
import {X, ExternalLink, AlertTriangle, Info, CheckCircle, AlertCircle} from "lucide-react";
import {useTranslation} from "react-i18next";
interface TermixAlert {
id: string;
title: string;
message: string;
expiresAt: string;
priority?: 'low' | 'medium' | 'high' | 'critical';
type?: 'info' | 'warning' | 'error' | 'success';
actionUrl?: string;
actionText?: string;
}
import type { TermixAlert } from '../../../types/index.js';
interface AlertCardProps {
alert: TermixAlert;

View File

@@ -3,17 +3,7 @@ import {HomepageAlertCard} from "./HomepageAlertCard.tsx";
import {Button} from "@/components/ui/button.tsx";
import { getUserAlerts, dismissAlert } from "@/ui/main-axios.ts";
import {useTranslation} from "react-i18next";
interface TermixAlert {
id: string;
title: string;
message: string;
expiresAt: string;
priority?: 'low' | 'medium' | 'high' | 'critical';
type?: 'info' | 'warning' | 'error' | 'success';
actionUrl?: string;
actionText?: string;
}
import type { TermixAlert } from '../../../types/index.js';
interface AlertManagerProps {
userId: string | null;

View File

@@ -5,33 +5,7 @@ import {ButtonGroup} from "@/components/ui/button-group.tsx";
import {Server, Terminal} from "lucide-react";
import {useTabs} from "@/ui/Desktop/Navigation/Tabs/TabContext.tsx";
import {getServerStatusById} from "@/ui/main-axios.ts";
interface SSHHost {
id: number;
name: string;
ip: string;
port: number;
username: string;
folder: string;
tags: string[];
pin: boolean;
authType: string;
password?: string;
key?: string;
keyPassword?: string;
keyType?: string;
enableTerminal: boolean;
enableTunnel: boolean;
enableFileManager: boolean;
defaultPath: string;
tunnelConnections: any[];
createdAt: string;
updatedAt: string;
}
interface HostProps {
host: SSHHost;
}
import type { SSHHost, HostProps } from '../../../types/index.js';
export function Host({host}: HostProps): React.ReactElement {
const {addTab} = useTabs();

View File

@@ -1,13 +1,8 @@
import React, {createContext, useContext, useState, useRef, type ReactNode} from 'react';
import {useTranslation} from 'react-i18next';
import type { TabContextTab } from '../../../types/index.js';
export interface Tab {
id: number;
type: 'home' | 'terminal' | 'ssh_manager' | 'server' | 'admin' | 'file_manager';
title: string;
hostConfig?: any;
terminalRef?: React.RefObject<any>;
}
export type Tab = TabContextTab;
interface TabContextType {
tabs: Tab[];