FIx frontend old confige editor paths
This commit is contained in:
@@ -7,15 +7,15 @@ import {Button} from '@/components/ui/button.tsx';
|
||||
import {FIleManagerTopNavbar} from "@/ui/apps/File Manager/FIleManagerTopNavbar.tsx";
|
||||
import {cn} from '@/lib/utils.ts';
|
||||
import {
|
||||
getConfigEditorRecent,
|
||||
getConfigEditorPinned,
|
||||
getConfigEditorShortcuts,
|
||||
addConfigEditorRecent,
|
||||
removeConfigEditorRecent,
|
||||
addConfigEditorPinned,
|
||||
removeConfigEditorPinned,
|
||||
addConfigEditorShortcut,
|
||||
removeConfigEditorShortcut,
|
||||
getFileManagerRecent,
|
||||
getFileManagerPinned,
|
||||
getFileManagerShortcuts,
|
||||
addFileManagerRecent,
|
||||
removeFileManagerRecent,
|
||||
addFileManagerPinned,
|
||||
removeFileManagerPinned,
|
||||
addFileManagerShortcut,
|
||||
removeFileManagerShortcut,
|
||||
readSSHFile,
|
||||
writeSSHFile,
|
||||
getSSHStatus,
|
||||
@@ -52,14 +52,18 @@ interface SSHHost {
|
||||
keyType?: string;
|
||||
enableTerminal: boolean;
|
||||
enableTunnel: boolean;
|
||||
enableConfigEditor: boolean;
|
||||
enableFileManager: boolean;
|
||||
defaultPath: string;
|
||||
tunnelConnections: any[];
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
}
|
||||
|
||||
export function FileManager({onSelectView, embedded = false, initialHost = null}: { onSelectView?: (view: string) => void, embedded?: boolean, initialHost?: SSHHost | null }): React.ReactElement {
|
||||
export function FileManager({onSelectView, embedded = false, initialHost = null}: {
|
||||
onSelectView?: (view: string) => void,
|
||||
embedded?: boolean,
|
||||
initialHost?: SSHHost | null
|
||||
}): React.ReactElement {
|
||||
const [tabs, setTabs] = useState<Tab[]>([]);
|
||||
const [activeTab, setActiveTab] = useState<string | number>('home');
|
||||
const [recent, setRecent] = useState<any[]>([]);
|
||||
@@ -118,9 +122,9 @@ export function FileManager({onSelectView, embedded = false, initialHost = null}
|
||||
|
||||
try {
|
||||
const homeDataPromise = Promise.all([
|
||||
getConfigEditorRecent(currentHost.id),
|
||||
getConfigEditorPinned(currentHost.id),
|
||||
getConfigEditorShortcuts(currentHost.id),
|
||||
getFileManagerRecent(currentHost.id),
|
||||
getFileManagerPinned(currentHost.id),
|
||||
getFileManagerShortcuts(currentHost.id),
|
||||
]);
|
||||
|
||||
const timeoutPromise = new Promise((_, reject) =>
|
||||
@@ -197,7 +201,7 @@ export function FileManager({onSelectView, embedded = false, initialHost = null}
|
||||
loading: false,
|
||||
error: undefined
|
||||
} : t));
|
||||
await addConfigEditorRecent({
|
||||
await addFileManagerRecent({
|
||||
name: file.name,
|
||||
path: file.path,
|
||||
isSSH: true,
|
||||
@@ -215,7 +219,7 @@ export function FileManager({onSelectView, embedded = false, initialHost = null}
|
||||
|
||||
const handleRemoveRecent = async (file: any) => {
|
||||
try {
|
||||
await removeConfigEditorRecent({
|
||||
await removeFileManagerRecent({
|
||||
name: file.name,
|
||||
path: file.path,
|
||||
isSSH: true,
|
||||
@@ -229,7 +233,7 @@ export function FileManager({onSelectView, embedded = false, initialHost = null}
|
||||
|
||||
const handlePinFile = async (file: any) => {
|
||||
try {
|
||||
await addConfigEditorPinned({
|
||||
await addFileManagerPinned({
|
||||
name: file.name,
|
||||
path: file.path,
|
||||
isSSH: true,
|
||||
@@ -246,7 +250,7 @@ export function FileManager({onSelectView, embedded = false, initialHost = null}
|
||||
|
||||
const handleUnpinFile = async (file: any) => {
|
||||
try {
|
||||
await removeConfigEditorPinned({
|
||||
await removeFileManagerPinned({
|
||||
name: file.name,
|
||||
path: file.path,
|
||||
isSSH: true,
|
||||
@@ -286,7 +290,7 @@ export function FileManager({onSelectView, embedded = false, initialHost = null}
|
||||
const handleAddShortcut = async (folderPath: string) => {
|
||||
try {
|
||||
const name = folderPath.split('/').pop() || folderPath;
|
||||
await addConfigEditorShortcut({
|
||||
await addFileManagerShortcut({
|
||||
name,
|
||||
path: folderPath,
|
||||
isSSH: true,
|
||||
@@ -300,7 +304,7 @@ export function FileManager({onSelectView, embedded = false, initialHost = null}
|
||||
|
||||
const handleRemoveShortcut = async (shortcut: any) => {
|
||||
try {
|
||||
await removeConfigEditorShortcut({
|
||||
await removeFileManagerShortcut({
|
||||
name: shortcut.name,
|
||||
path: shortcut.path,
|
||||
isSSH: true,
|
||||
@@ -402,7 +406,7 @@ export function FileManager({onSelectView, embedded = false, initialHost = null}
|
||||
Promise.allSettled([
|
||||
(async () => {
|
||||
try {
|
||||
await addConfigEditorRecent({
|
||||
await addFileManagerRecent({
|
||||
name: tab.fileName,
|
||||
path: tab.filePath,
|
||||
isSSH: true,
|
||||
@@ -445,14 +449,16 @@ export function FileManager({onSelectView, embedded = false, initialHost = null}
|
||||
};
|
||||
|
||||
// Host is locked; no external host change from UI
|
||||
const handleHostChange = (_host: SSHHost | null) => {};
|
||||
const handleHostChange = (_host: SSHHost | null) => {
|
||||
};
|
||||
|
||||
if (!currentHost) {
|
||||
return (
|
||||
<div style={{position: 'relative', width: '100%', height: '100%', overflow: 'hidden'}}>
|
||||
<div style={{position: 'absolute', top: 0, left: 0, width: 256, height: '100%', zIndex: 20}}>
|
||||
<FileManagerLeftSidebar
|
||||
onSelectView={onSelectView || (() => {})}
|
||||
onSelectView={onSelectView || (() => {
|
||||
})}
|
||||
onOpenFile={handleOpenFile}
|
||||
tabs={tabs}
|
||||
ref={sidebarRef}
|
||||
@@ -483,7 +489,8 @@ export function FileManager({onSelectView, embedded = false, initialHost = null}
|
||||
<div style={{position: 'relative', width: '100%', height: '100%', overflow: 'hidden'}}>
|
||||
<div style={{position: 'absolute', top: 0, left: 0, width: 256, height: '100%', zIndex: 20}}>
|
||||
<FileManagerLeftSidebar
|
||||
onSelectView={onSelectView || (() => {})}
|
||||
onSelectView={onSelectView || (() => {
|
||||
})}
|
||||
onOpenFile={handleOpenFile}
|
||||
tabs={tabs}
|
||||
ref={sidebarRef}
|
||||
|
||||
@@ -5,13 +5,13 @@ import {hyperLink} from '@uiw/codemirror-extensions-hyper-link';
|
||||
import {oneDark} from '@codemirror/theme-one-dark';
|
||||
import {EditorView} from '@codemirror/view';
|
||||
|
||||
interface ConfigCodeEditorProps {
|
||||
interface FileManagerCodeEditorProps {
|
||||
content: string;
|
||||
fileName: string;
|
||||
onContentChange: (value: string) => void;
|
||||
}
|
||||
|
||||
export function FileManagerFileEditor({content, fileName, onContentChange}: ConfigCodeEditorProps) {
|
||||
export function FileManagerFileEditor({content, fileName, onContentChange}: FileManagerCodeEditorProps) {
|
||||
function getLanguageName(filename: string): string {
|
||||
if (!filename || typeof filename !== 'string') {
|
||||
return 'text';
|
||||
|
||||
@@ -18,7 +18,7 @@ interface ShortcutItem {
|
||||
path: string;
|
||||
}
|
||||
|
||||
interface ConfigHomeViewProps {
|
||||
interface FileManagerHomeViewProps {
|
||||
recent: FileItem[];
|
||||
pinned: FileItem[];
|
||||
shortcuts: ShortcutItem[];
|
||||
@@ -42,7 +42,7 @@ export function FileManagerHomeView({
|
||||
onOpenShortcut,
|
||||
onRemoveShortcut,
|
||||
onAddShortcut
|
||||
}: ConfigHomeViewProps) {
|
||||
}: FileManagerHomeViewProps) {
|
||||
const [tab, setTab] = useState<'recent' | 'pinned' | 'shortcuts'>('recent');
|
||||
const [newShortcut, setNewShortcut] = useState('');
|
||||
|
||||
|
||||
@@ -9,9 +9,9 @@ import {
|
||||
listSSHFiles,
|
||||
connectSSH,
|
||||
getSSHStatus,
|
||||
getConfigEditorPinned,
|
||||
addConfigEditorPinned,
|
||||
removeConfigEditorPinned
|
||||
getFileManagerPinned,
|
||||
addFileManagerPinned,
|
||||
removeFileManagerPinned
|
||||
} from '@/ui/main-axios.ts';
|
||||
|
||||
interface SSHHost {
|
||||
@@ -30,14 +30,14 @@ interface SSHHost {
|
||||
keyType?: string;
|
||||
enableTerminal: boolean;
|
||||
enableTunnel: boolean;
|
||||
enableConfigEditor: boolean;
|
||||
enableFileManager: boolean;
|
||||
defaultPath: string;
|
||||
tunnelConnections: any[];
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
}
|
||||
|
||||
const FileManagerLeftSidebar = forwardRef(function ConfigEditorSidebar(
|
||||
const FileManagerLeftSidebar = forwardRef(function FileManagerSidebar(
|
||||
{onSelectView, onOpenFile, tabs, host}: {
|
||||
onSelectView?: (view: string) => void;
|
||||
onOpenFile: (file: any) => void;
|
||||
@@ -146,7 +146,7 @@ const FileManagerLeftSidebar = forwardRef(function ConfigEditorSidebar(
|
||||
let pinnedFiles: any[] = [];
|
||||
try {
|
||||
if (host) {
|
||||
pinnedFiles = await getConfigEditorPinned(host.id);
|
||||
pinnedFiles = await getFileManagerPinned(host.id);
|
||||
}
|
||||
} catch (err) {
|
||||
}
|
||||
@@ -336,7 +336,7 @@ const FileManagerLeftSidebar = forwardRef(function ConfigEditorSidebar(
|
||||
e.stopPropagation();
|
||||
try {
|
||||
if (item.isPinned) {
|
||||
await removeConfigEditorPinned({
|
||||
await removeFileManagerPinned({
|
||||
name: item.name,
|
||||
path: item.path,
|
||||
hostId: host?.id,
|
||||
@@ -347,7 +347,7 @@ const FileManagerLeftSidebar = forwardRef(function ConfigEditorSidebar(
|
||||
f.path === item.path ? { ...f, isPinned: false } : f
|
||||
));
|
||||
} else {
|
||||
await addConfigEditorPinned({
|
||||
await addFileManagerPinned({
|
||||
name: item.name,
|
||||
path: item.path,
|
||||
hostId: host?.id,
|
||||
|
||||
@@ -20,7 +20,7 @@ interface FileItem {
|
||||
isStarred?: boolean;
|
||||
}
|
||||
|
||||
interface ConfigFileSidebarViewerProps {
|
||||
interface FileManagerLeftSidebarVileViewerProps {
|
||||
sshConnections: SSHConnection[];
|
||||
onAddSSH: () => void;
|
||||
onConnectSSH: (conn: SSHConnection) => void;
|
||||
@@ -60,7 +60,7 @@ export function FileManagerLeftSidebarFileViewer({
|
||||
onSwitchToLocal,
|
||||
onSwitchToSSH,
|
||||
currentSSH,
|
||||
}: ConfigFileSidebarViewerProps) {
|
||||
}: FileManagerLeftSidebarVileViewerProps) {
|
||||
return (
|
||||
<div className="flex flex-col h-full">
|
||||
{/* SSH Connections */}
|
||||
|
||||
@@ -2,20 +2,20 @@ import React from 'react';
|
||||
import {Button} from '@/components/ui/button.tsx';
|
||||
import {X, Home} from 'lucide-react';
|
||||
|
||||
interface ConfigTab {
|
||||
interface FileManagerTab {
|
||||
id: string | number;
|
||||
title: string;
|
||||
}
|
||||
|
||||
interface ConfigTabListProps {
|
||||
tabs: ConfigTab[];
|
||||
interface FileManagerTabList {
|
||||
tabs: FileManagerTab[];
|
||||
activeTab: string | number;
|
||||
setActiveTab: (tab: string | number) => void;
|
||||
closeTab: (tab: string | number) => void;
|
||||
onHomeClick: () => void;
|
||||
}
|
||||
|
||||
export function FileManagerTabList({tabs, activeTab, setActiveTab, closeTab, onHomeClick}: ConfigTabListProps) {
|
||||
export function FileManagerTabList({tabs, activeTab, setActiveTab, closeTab, onHomeClick}: FileManagerTabList) {
|
||||
return (
|
||||
<div className="inline-flex items-center h-full px-[0.5rem] overflow-x-auto">
|
||||
<Button
|
||||
|
||||
@@ -5,7 +5,7 @@ import {Separator} from "@/components/ui/separator.tsx";
|
||||
import {HostManagerHostEditor} from "@/ui/apps/Host Manager/HostManagerHostEditor.tsx";
|
||||
import {useSidebar} from "@/components/ui/sidebar.tsx";
|
||||
|
||||
interface ConfigEditorProps {
|
||||
interface HostManagerProps {
|
||||
onSelectView: (view: string) => void;
|
||||
isTopbarOpen?: boolean;
|
||||
}
|
||||
@@ -26,14 +26,14 @@ interface SSHHost {
|
||||
keyType?: string;
|
||||
enableTerminal: boolean;
|
||||
enableTunnel: boolean;
|
||||
enableConfigEditor: boolean;
|
||||
enableFileManager: boolean;
|
||||
defaultPath: string;
|
||||
tunnelConnections: any[];
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
}
|
||||
|
||||
export function HostManager({onSelectView, isTopbarOpen}: ConfigEditorProps): React.ReactElement {
|
||||
export function HostManager({onSelectView, isTopbarOpen}: HostManagerProps): React.ReactElement {
|
||||
const [activeTab, setActiveTab] = useState("host_viewer");
|
||||
const [editingHost, setEditingHost] = useState<SSHHost | null>(null);
|
||||
const {state: sidebarState} = useSidebar();
|
||||
|
||||
@@ -37,7 +37,7 @@ interface SSHHost {
|
||||
keyType?: string;
|
||||
enableTerminal: boolean;
|
||||
enableTunnel: boolean;
|
||||
enableConfigEditor: boolean;
|
||||
enableFileManager: boolean;
|
||||
defaultPath: string;
|
||||
tunnelConnections: any[];
|
||||
createdAt: string;
|
||||
@@ -120,7 +120,7 @@ export function HostManagerHostEditor({editingHost, onFormSubmit}: SSHManagerHos
|
||||
retryInterval: z.coerce.number().min(1).max(3600).default(10),
|
||||
autoStart: z.boolean().default(false),
|
||||
})).default([]),
|
||||
enableConfigEditor: z.boolean().default(true),
|
||||
enableFileManager: z.boolean().default(true),
|
||||
defaultPath: z.string().optional(),
|
||||
}).superRefine((data, ctx) => {
|
||||
if (data.authType === 'password') {
|
||||
@@ -178,7 +178,7 @@ export function HostManagerHostEditor({editingHost, onFormSubmit}: SSHManagerHos
|
||||
keyType: "auto",
|
||||
enableTerminal: editingHost?.enableTerminal !== false,
|
||||
enableTunnel: editingHost?.enableTunnel !== false,
|
||||
enableConfigEditor: editingHost?.enableConfigEditor !== false,
|
||||
enableFileManager: editingHost?.enableFileManager !== false,
|
||||
defaultPath: editingHost?.defaultPath || "/",
|
||||
tunnelConnections: editingHost?.tunnelConnections || [],
|
||||
}
|
||||
@@ -205,7 +205,7 @@ export function HostManagerHostEditor({editingHost, onFormSubmit}: SSHManagerHos
|
||||
keyType: (editingHost.keyType as any) || "auto",
|
||||
enableTerminal: editingHost.enableTerminal !== false,
|
||||
enableTunnel: editingHost.enableTunnel !== false,
|
||||
enableConfigEditor: editingHost.enableConfigEditor !== false,
|
||||
enableFileManager: editingHost.enableFileManager !== false,
|
||||
defaultPath: editingHost.defaultPath || "/",
|
||||
tunnelConnections: editingHost.tunnelConnections || [],
|
||||
});
|
||||
@@ -227,7 +227,7 @@ export function HostManagerHostEditor({editingHost, onFormSubmit}: SSHManagerHos
|
||||
keyType: "auto",
|
||||
enableTerminal: true,
|
||||
enableTunnel: true,
|
||||
enableConfigEditor: true,
|
||||
enableFileManager: true,
|
||||
defaultPath: "/",
|
||||
tunnelConnections: [],
|
||||
});
|
||||
@@ -989,7 +989,7 @@ export function HostManagerHostEditor({editingHost, onFormSubmit}: SSHManagerHos
|
||||
<TabsContent value="file_manager">
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="enableConfigEditor"
|
||||
name="enableFileManager"
|
||||
render={({field}) => (
|
||||
<FormItem>
|
||||
<FormLabel>Enable File Manager</FormLabel>
|
||||
@@ -1006,7 +1006,7 @@ export function HostManagerHostEditor({editingHost, onFormSubmit}: SSHManagerHos
|
||||
)}
|
||||
/>
|
||||
|
||||
{form.watch('enableConfigEditor') && (
|
||||
{form.watch('enableFileManager') && (
|
||||
<div className="mt-4">
|
||||
<FormField
|
||||
control={form.control}
|
||||
|
||||
@@ -35,7 +35,7 @@ interface SSHHost {
|
||||
authType: string;
|
||||
enableTerminal: boolean;
|
||||
enableTunnel: boolean;
|
||||
enableConfigEditor: boolean;
|
||||
enableFileManager: boolean;
|
||||
defaultPath: string;
|
||||
tunnelConnections: any[];
|
||||
createdAt: string;
|
||||
@@ -275,7 +275,7 @@ export function HostManagerHostViewer({onEditHost}: SSHManagerHostViewerProps) {
|
||||
pin: true,
|
||||
enableTerminal: true,
|
||||
enableTunnel: false,
|
||||
enableConfigEditor: true,
|
||||
enableFileManager: true,
|
||||
defaultPath: "/var/www"
|
||||
},
|
||||
{
|
||||
@@ -292,7 +292,7 @@ export function HostManagerHostViewer({onEditHost}: SSHManagerHostViewerProps) {
|
||||
pin: false,
|
||||
enableTerminal: true,
|
||||
enableTunnel: true,
|
||||
enableConfigEditor: false,
|
||||
enableFileManager: false,
|
||||
tunnelConnections: [
|
||||
{
|
||||
sourcePort: 5432,
|
||||
@@ -347,7 +347,7 @@ OPTIONAL FIELDS:
|
||||
• pin: Pin to top (boolean)
|
||||
• enableTerminal: Show in Terminal tab (boolean, default: true)
|
||||
• enableTunnel: Show in Tunnel tab (boolean, default: true)
|
||||
• enableConfigEditor: Show in Config Editor tab (boolean, default: true)
|
||||
• enableFileManager: Show in File Manager tab (boolean, default: true)
|
||||
• defaultPath: Default directory path (string)
|
||||
|
||||
TUNNEL CONFIGURATION:
|
||||
@@ -374,7 +374,7 @@ EXAMPLE STRUCTURE:
|
||||
"pin": true,
|
||||
"enableTerminal": true,
|
||||
"enableTunnel": false,
|
||||
"enableConfigEditor": true,
|
||||
"enableFileManager": true,
|
||||
"defaultPath": "/var/www"
|
||||
}
|
||||
]
|
||||
@@ -500,8 +500,8 @@ EXAMPLE STRUCTURE:
|
||||
<button class="copy-btn" onclick="navigator.clipboard.writeText('enableTunnel')">Copy</button>
|
||||
</div>
|
||||
<div class="field-item">
|
||||
<code>enableConfigEditor</code> - Show in Config Editor tab (boolean, default: true)
|
||||
<button class="copy-btn" onclick="navigator.clipboard.writeText('enableConfigEditor')">Copy</button>
|
||||
<code>enableFileManager</code> - Show in File Manager tab (boolean, default: true)
|
||||
<button class="copy-btn" onclick="navigator.clipboard.writeText('enableFileManager')">Copy</button>
|
||||
</div>
|
||||
<div class="field-item">
|
||||
<code>defaultPath</code> - Default directory path (string)
|
||||
@@ -558,7 +558,7 @@ EXAMPLE STRUCTURE:
|
||||
"pin": true,
|
||||
"enableTerminal": true,
|
||||
"enableTunnel": false,
|
||||
"enableConfigEditor": true,
|
||||
"enableFileManager": true,
|
||||
"defaultPath": "/var/www"
|
||||
}
|
||||
]
|
||||
@@ -709,10 +709,10 @@ EXAMPLE STRUCTURE:
|
||||
)}
|
||||
</Badge>
|
||||
)}
|
||||
{host.enableConfigEditor && (
|
||||
{host.enableFileManager && (
|
||||
<Badge variant="outline" className="text-xs px-1 py-0">
|
||||
<FileEdit className="h-2 w-2 mr-0.5"/>
|
||||
Config
|
||||
File Manager
|
||||
</Badge>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -145,7 +145,7 @@ export function Server({ hostConfig, title, isVisible = true, isTopbarOpen = tru
|
||||
</Status>
|
||||
</div>
|
||||
<div className="flex items-center">
|
||||
{currentHostConfig?.enableConfigEditor && (
|
||||
{currentHostConfig?.enableFileManager && (
|
||||
<Button
|
||||
variant="outline"
|
||||
className="font-semibold"
|
||||
@@ -155,7 +155,7 @@ export function Server({ hostConfig, title, isVisible = true, isTopbarOpen = tru
|
||||
? currentHostConfig.name.trim()
|
||||
: `${currentHostConfig.username}@${currentHostConfig.ip}`;
|
||||
addTab({
|
||||
type: 'config',
|
||||
type: 'file_manager',
|
||||
title: titleBase,
|
||||
hostConfig: currentHostConfig,
|
||||
});
|
||||
@@ -194,7 +194,7 @@ export function Server({ hostConfig, title, isVisible = true, isTopbarOpen = tru
|
||||
|
||||
{/* Memory */}
|
||||
<div className="flex-1 min-w-0 px-2 py-2">
|
||||
<h1 className="font-bold text-lg flex flex-row gap-2 mb-1">
|
||||
<h1 className="font-bold xt-lg flex flex-row gap-2 mb-1">
|
||||
<MemoryStick/>
|
||||
{(() => {
|
||||
const pct = metrics?.memory?.percent;
|
||||
|
||||
@@ -27,7 +27,7 @@ interface SSHHost {
|
||||
keyType?: string;
|
||||
enableTerminal: boolean;
|
||||
enableTunnel: boolean;
|
||||
enableConfigEditor: boolean;
|
||||
enableFileManager: boolean;
|
||||
defaultPath: string;
|
||||
tunnelConnections: TunnelConnection[];
|
||||
createdAt: string;
|
||||
|
||||
@@ -53,7 +53,7 @@ interface SSHHost {
|
||||
authType: string;
|
||||
enableTerminal: boolean;
|
||||
enableTunnel: boolean;
|
||||
enableConfigEditor: boolean;
|
||||
enableFileManager: boolean;
|
||||
defaultPath: string;
|
||||
tunnelConnections: TunnelConnection[];
|
||||
createdAt: string;
|
||||
|
||||
@@ -22,7 +22,7 @@ interface SSHHost {
|
||||
authType: string;
|
||||
enableTerminal: boolean;
|
||||
enableTunnel: boolean;
|
||||
enableConfigEditor: boolean;
|
||||
enableFileManager: boolean;
|
||||
defaultPath: string;
|
||||
tunnelConnections: TunnelConnection[];
|
||||
createdAt: string;
|
||||
|
||||
Reference in New Issue
Block a user