169 lines
5.1 KiB
TypeScript
169 lines
5.1 KiB
TypeScript
import type { Connection, SessionTreeLayoutPayload, SessionTreeNodePayload } from '../types'
|
|
|
|
export interface BuiltTreeNode {
|
|
id: string
|
|
type: 'folder' | 'connection'
|
|
name: string
|
|
order: number
|
|
parentId: string | null
|
|
expanded?: boolean
|
|
connection?: Connection
|
|
children: BuiltTreeNode[]
|
|
}
|
|
|
|
export function cn(...parts: Array<string | false | null | undefined>) {
|
|
return parts.filter(Boolean).join(' ')
|
|
}
|
|
|
|
export function formatBytes(bytes?: number | null) {
|
|
if (bytes == null || Number.isNaN(bytes)) return '-'
|
|
if (bytes < 1024) return `${bytes} B`
|
|
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`
|
|
if (bytes < 1024 * 1024 * 1024) return `${(bytes / (1024 * 1024)).toFixed(1)} MB`
|
|
return `${(bytes / (1024 * 1024 * 1024)).toFixed(1)} GB`
|
|
}
|
|
|
|
export function formatTimestamp(timestamp?: number | null) {
|
|
if (!timestamp) return '-'
|
|
return new Date(timestamp).toLocaleString()
|
|
}
|
|
|
|
export function formatSftpDate(timestampMs?: number | null) {
|
|
if (timestampMs == null || Number.isNaN(timestampMs)) return '-'
|
|
|
|
const date = new Date(timestampMs)
|
|
if (Number.isNaN(date.getTime())) return '-'
|
|
|
|
const year = String(date.getFullYear())
|
|
const month = String(date.getMonth() + 1).padStart(2, '0')
|
|
const day = String(date.getDate()).padStart(2, '0')
|
|
const hours = String(date.getHours()).padStart(2, '0')
|
|
const minutes = String(date.getMinutes()).padStart(2, '0')
|
|
const seconds = String(date.getSeconds()).padStart(2, '0')
|
|
|
|
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`
|
|
}
|
|
|
|
export function formatSftpPermissions(entry: { directory: boolean }) {
|
|
return entry.directory ? 'drwxr-xr-x' : '-rw-r--r--'
|
|
}
|
|
|
|
export function buildSessionTree(layout: SessionTreeLayoutPayload | null, connections: Connection[]): BuiltTreeNode[] {
|
|
const connectionMap = new Map(connections.map((item) => [item.id, item]))
|
|
const nodes = layout?.nodes ?? []
|
|
if (nodes.length === 0) {
|
|
return connections
|
|
.slice()
|
|
.sort((a, b) => a.name.localeCompare(b.name))
|
|
.map((connection, index) => ({
|
|
id: `connection-${connection.id}`,
|
|
type: 'connection',
|
|
name: connection.name,
|
|
order: index,
|
|
parentId: null,
|
|
connection,
|
|
children: [],
|
|
}))
|
|
}
|
|
|
|
const built = new Map<string, BuiltTreeNode>()
|
|
for (const node of nodes) {
|
|
built.set(node.id, {
|
|
id: node.id,
|
|
type: node.type,
|
|
name: node.name,
|
|
order: node.order,
|
|
parentId: node.parentId,
|
|
expanded: node.expanded,
|
|
connection: node.connectionId ? connectionMap.get(node.connectionId) : undefined,
|
|
children: [],
|
|
})
|
|
}
|
|
|
|
const roots: BuiltTreeNode[] = []
|
|
for (const node of nodes) {
|
|
const current = built.get(node.id)
|
|
if (!current) continue
|
|
if (node.type === 'connection' && node.connectionId && !current.connection) continue
|
|
if (node.parentId && built.has(node.parentId)) {
|
|
built.get(node.parentId)!.children.push(current)
|
|
} else {
|
|
roots.push(current)
|
|
}
|
|
}
|
|
|
|
const sortNodes = (items: BuiltTreeNode[]) => {
|
|
items.sort((a, b) => a.order - b.order || a.name.localeCompare(b.name))
|
|
items.forEach((item) => sortNodes(item.children))
|
|
}
|
|
sortNodes(roots)
|
|
return roots
|
|
}
|
|
|
|
export function collectConnectionIds(nodes: BuiltTreeNode[]) {
|
|
const ids: number[] = []
|
|
const walk = (list: BuiltTreeNode[]) => {
|
|
list.forEach((node) => {
|
|
if (node.type === 'connection' && node.connection) ids.push(node.connection.id)
|
|
if (node.children.length > 0) walk(node.children)
|
|
})
|
|
}
|
|
walk(nodes)
|
|
return ids
|
|
}
|
|
|
|
export function updateExpandedState(layout: SessionTreeLayoutPayload | null, nodeId: string): SessionTreeLayoutPayload | null {
|
|
if (!layout) return layout
|
|
return {
|
|
...layout,
|
|
nodes: layout.nodes.map((node) =>
|
|
node.id === nodeId && node.type === 'folder'
|
|
? { ...node, expanded: !node.expanded, updatedAt: Date.now() }
|
|
: node,
|
|
),
|
|
}
|
|
}
|
|
|
|
export function buildDefaultTreeLayout(connections: Connection[]): SessionTreeLayoutPayload {
|
|
const now = Date.now()
|
|
const groups = new Map<string, Connection[]>()
|
|
connections.forEach((connection) => {
|
|
const key = connection.name.includes('-') ? connection.name.split('-')[0] : '默认分组'
|
|
const group = groups.get(key) ?? []
|
|
group.push(connection)
|
|
groups.set(key, group)
|
|
})
|
|
|
|
const nodes: SessionTreeNodePayload[] = []
|
|
let order = 0
|
|
Array.from(groups.entries()).forEach(([groupName, list]) => {
|
|
const folderId = `folder-${groupName}-${order}`
|
|
nodes.push({
|
|
id: folderId,
|
|
type: 'folder',
|
|
name: groupName,
|
|
parentId: null,
|
|
order,
|
|
expanded: true,
|
|
createdAt: now,
|
|
updatedAt: now,
|
|
})
|
|
list
|
|
.sort((a, b) => a.name.localeCompare(b.name))
|
|
.forEach((connection, index) => {
|
|
nodes.push({
|
|
id: `connection-${connection.id}`,
|
|
type: 'connection',
|
|
name: connection.name,
|
|
parentId: folderId,
|
|
order: index,
|
|
connectionId: connection.id,
|
|
createdAt: now,
|
|
updatedAt: now,
|
|
})
|
|
})
|
|
order += 1
|
|
})
|
|
return { nodes, sortMode: 'manual' }
|
|
}
|