Skip to content

Commit

Permalink
feat: devboxListView refactor
Browse files Browse the repository at this point in the history
  • Loading branch information
mlhiter committed Nov 6, 2024
1 parent f844e16 commit 5e72a83
Show file tree
Hide file tree
Showing 9 changed files with 225 additions and 117 deletions.
1 change: 1 addition & 0 deletions extensions/ide/vscode/devbox/images/delete.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
22 changes: 12 additions & 10 deletions extensions/ide/vscode/devbox/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -58,22 +58,23 @@
"title": "Create Devbox",
"icon": "images/create.svg"
},
{
"command": "devboxDashboard.deleteDevbox",
"title": "Delete Devbox"
},
{
"command": "devboxDashboard.openDevbox",
"title": "Open Devbox",
"icon": "images/open.svg"
},
{
"command": "devboxDashboard.deleteDevbox",
"title": "Delete Devbox",
"icon": "images/delete.svg"
},
{
"command": "devbox.openExternalLink",
"title": "Devbox:Open in Browser"
}
],
"views": {
"devboxView": [
"devboxListView": [
{
"id": "devboxDashboard",
"name": "My Projects"
Expand All @@ -99,7 +100,7 @@
"viewsContainers": {
"activitybar": [
{
"id": "devboxView",
"id": "devboxListView",
"title": "Devbox",
"icon": "images/explorer.svg"
}
Expand Down Expand Up @@ -138,13 +139,14 @@
],
"view/item/context": [
{
"command": "devboxDashboard.deleteDevbox",
"when": "view == devboxDashboard && viewItem == devbox"
"command": "devboxDashboard.openDevbox",
"when": "view == devboxDashboard && viewItem == devbox",
"group": "inline@1"
},
{
"command": "devboxDashboard.openDevbox",
"command": "devboxDashboard.deleteDevbox",
"when": "view == devboxDashboard && viewItem == devbox",
"group": "inline"
"group": "inline@2"
}
]
}
Expand Down
51 changes: 27 additions & 24 deletions extensions/ide/vscode/devbox/src/api/ssh.ts
Original file line number Diff line number Diff line change
@@ -1,49 +1,52 @@
const fs = require('fs')
import fs from 'fs'

export const parseSSHConfig = (configFilePath: string) => {
import { GlobalStateManager } from '../utils/globalStateManager'

export const parseSSHConfig = (filePath: string) => {
return new Promise((resolve, reject) => {
fs.readFile(configFilePath, 'utf-8', (err: any, data: any) => {
fs.readFile(filePath, 'utf-8', (err: any, data: any) => {
if (err) {
return reject(err)
}

const lines = data.split('\n')
const devboxList = [] as any[]
let currentHost = {} as any
let lastComment = ''
let currentHostObj = {} as any

lines.forEach((line: string) => {
line = line.trim()

if (line.startsWith('#')) {
// 保存注释,特别是 WorkingDir 注释
lastComment = line
if (line.startsWith('# WorkingDir:')) {
currentHost.remotePath = line.split(':')[1].trim()
}
} else if (line.startsWith('Host ')) {
// 如果当前有主机信息且是 usw.sailos.io,则保存
if (currentHost.hostName === 'usw.sailos.io') {
devboxList.push(currentHost)
if (line.startsWith('Host ')) {
// TODO:这里改成注入,而不是硬编码
if (!!currentHostObj.StrictHostKeyChecking) {
currentHostObj.remotePath =
GlobalStateManager.getWorkDir('remotePath')
devboxList.push(currentHostObj)
}
// 开始新的主机信息
currentHost = { host: line.split(' ')[1] }
currentHostObj = { host: line.split(' ')[1] }
} else if (line.startsWith('HostName ')) {
currentHost.hostName = line.split(' ')[1]
currentHostObj.hostName = line.split(' ')[1]
} else if (line.startsWith('User ')) {
currentHost.user = line.split(' ')[1]
currentHostObj.user = line.split(' ')[1]
} else if (line.startsWith('Port ')) {
currentHost.port = line.split(' ')[1]
currentHostObj.port = line.split(' ')[1]
} else if (line.startsWith('IdentityFile ')) {
currentHost.identityFile = line.split(' ')[1]
currentHostObj.identityFile = line.split(' ')[1]
} else if (line.startsWith('IdentitiesOnly ')) {
currentHostObj.IdentitiesOnly = line.split(' ')[1]
} else if (line.startsWith('StrictHostKeyChecking ')) {
currentHostObj.StrictHostKeyChecking = line.split(' ')[1]
}
})

// 最后一个主机信息处理
if (currentHost.hostName === 'usw.sailos.io') {
devboxList.push(currentHost)
// the last one
if (!!currentHostObj.StrictHostKeyChecking) {
currentHostObj.remotePath = GlobalStateManager.getWorkDir('remotePath')
devboxList.push(currentHostObj)
}

console.log(devboxList)

resolve(devboxList)
})
})
Expand Down
79 changes: 27 additions & 52 deletions extensions/ide/vscode/devbox/src/commands/remoteConnector.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import path from 'path'
import * as os from 'os'
import * as fs from 'fs'
import * as vscode from 'vscode'
Expand All @@ -7,13 +6,15 @@ import { execSync } from 'child_process'

import { Disposable } from '../common/dispose'
import { modifiedRemoteSSHConfig } from '../utils/remoteSSHConfig'

const defaultSSHConfigPath = path.resolve(os.homedir(), '.ssh/config')
const defaultDevboxSSHConfigPath = path.resolve(
os.homedir(),
'.ssh/sealos/devbox_config'
)
const defaultSSHKeyPath = path.resolve(os.homedir(), '.ssh/sealos')
import {
defaultSSHConfigPath,
defaultDevboxSSHConfigPath,
defaultSSHKeyPath,
} from '../constant/file'
import {
convertSSHConfigToVersion2,
ensureFileExists,
} from '../utils/sshConfig'

export class RemoteSSHConnector extends Disposable {
constructor(context: vscode.ExtensionContext) {
Expand All @@ -26,6 +27,22 @@ export class RemoteSSHConnector extends Disposable {
)
}
}
private sshConfigPreProcess() {
// 1. ensure .ssh/config exists
ensureFileExists(defaultSSHConfigPath, '.ssh')
// 2. ensure .ssh/sealos/devbox_config exists
ensureFileExists(defaultDevboxSSHConfigPath, '.ssh/sealos')
// 3. ensure .ssh/config includes .ssh/sealos/devbox_config
const existingSSHConfig = fs.readFileSync(defaultSSHConfigPath, 'utf8')
if (!existingSSHConfig.includes('Include ~/.ssh/sealos/devbox_config')) {
let existingSSHConfig = fs.readFileSync(defaultSSHConfigPath, 'utf-8')
const newConfig =
'Include ~/.ssh/sealos/devbox_config\n' + existingSSHConfig
fs.writeFileSync(defaultSSHConfigPath, newConfig)
}
// 4. ensure sshConfig from version1 to version2
convertSSHConfigToVersion2(defaultDevboxSSHConfigPath)
}

private async connectRemoteSSH(args: {
sshDomain: string
Expand Down Expand Up @@ -59,51 +76,9 @@ export class RemoteSSHConnector extends Disposable {
})
const sshConfigString = SSHConfig.stringify(sshConfig)

try {
// 1. ensure .ssh/config exists
if (!fs.existsSync(defaultSSHConfigPath)) {
fs.mkdirSync(path.resolve(os.homedir(), '.ssh'), {
recursive: true,
})
fs.writeFileSync(defaultSSHConfigPath, '', 'utf8')
// .ssh/config authority
if (os.platform() === 'win32') {
// Windows
execSync(`icacls "${defaultSSHConfigPath}" /inheritance:r`)
execSync(
`icacls "${defaultSSHConfigPath}" /grant:r ${process.env.USERNAME}:F`
)
execSync(`icacls "${defaultSSHConfigPath}" /remove:g everyone`)
} else {
// Unix-like system (Mac, Linux)
execSync(`chmod 600 "${defaultSSHConfigPath}"`)
}
}
// 2. ensure .ssh/sealos/devbox_config exists and has the correct authority
if (!fs.existsSync(defaultDevboxSSHConfigPath)) {
fs.mkdirSync(path.resolve(os.homedir(), '.ssh/sealos'), {
recursive: true,
})
fs.writeFileSync(defaultDevboxSSHConfigPath, '', 'utf8')
if (os.platform() === 'win32') {
execSync(`icacls "${defaultDevboxSSHConfigPath}" /inheritance:r`)
execSync(
`icacls "${defaultDevboxSSHConfigPath}" /grant:r ${process.env.USERNAME}:F`
)
execSync(`icacls "${defaultDevboxSSHConfigPath}" /remove:g everyone`)
} else {
execSync(`chmod 600 "${defaultDevboxSSHConfigPath}"`)
}
}
// 3. ensure .ssh/config includes .ssh/sealos/devbox_config
const existingSSHConfig = fs.readFileSync(defaultSSHConfigPath, 'utf8')
if (!existingSSHConfig.includes('Include ~/.ssh/sealos/devbox_config')) {
let existingSSHConfig = fs.readFileSync(defaultSSHConfigPath, 'utf-8')
const newConfig =
'Include ~/.ssh/sealos/devbox_config\n' + existingSSHConfig
fs.writeFileSync(defaultSSHConfigPath, newConfig)
}
this.sshConfigPreProcess()

try {
// 读取现有的 devbox 配置文件
const existingDevboxConfigLines = fs
.readFileSync(defaultDevboxSSHConfigPath, 'utf8')
Expand Down
9 changes: 9 additions & 0 deletions extensions/ide/vscode/devbox/src/constant/file.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import path from 'path'
import * as os from 'os'

export const defaultSSHConfigPath = path.resolve(os.homedir(), '.ssh/config')
export const defaultDevboxSSHConfigPath = path.resolve(
os.homedir(),
'.ssh/sealos/devbox_config'
)
export const defaultSSHKeyPath = path.resolve(os.homedir(), '.ssh/sealos')
8 changes: 4 additions & 4 deletions extensions/ide/vscode/devbox/src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import * as vscode from 'vscode'

import { Webview } from './commands/webview'
import { RemoteSSHConnector } from './commands/remoteConnector'
import { TreeView } from './commands/treeview'
import { DevboxListViewProvider } from './providers/DevboxListViewProvider'
import { UriHandler } from './utils/handleUri'
import { NetworkViewProvider } from './providers/NetworkViewProvider'
import { DBViewProvider } from './providers/DbViewProvider'
Expand All @@ -22,9 +22,9 @@ export async function activate(context: vscode.ExtensionContext) {
const remoteConnector = new RemoteSSHConnector(context)
context.subscriptions.push(remoteConnector)

// tree view
const treeView = new TreeView(context)
context.subscriptions.push(treeView)
// devboxList view
const devboxListViewProvider = new DevboxListViewProvider(context)
context.subscriptions.push(devboxListViewProvider)

// token manager
GlobalStateManager.init(context)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,35 +1,32 @@
import path from 'path'
import * as os from 'os'
import * as vscode from 'vscode'

import { parseSSHConfig } from '../api/ssh'
import { Disposable } from '../common/dispose'
import { DevboxListItem } from '../types/devbox'
import { defaultDevboxSSHConfigPath } from '../constant/file'

export class TreeView extends Disposable {
export class DevboxListViewProvider extends Disposable {
constructor(context: vscode.ExtensionContext) {
super()
if (context.extension.extensionKind === vscode.ExtensionKind.UI) {
// view
const projectTreeDataProvider = new MyTreeDataProvider('devboxDashboard')
// TODO: 完善 feedback部分
const feedbackTreeDataProvider = new MyTreeDataProvider('devboxFeedback')
// views
const devboxDashboardView = vscode.window.createTreeView(
'devboxDashboard',
{
treeDataProvider: projectTreeDataProvider,
}
)
this._register(devboxDashboardView)

// 添加视图可见性变化事件监听器
this._register(
devboxDashboardView.onDidChangeVisibility(() => {
if (devboxDashboardView.visible) {
projectTreeDataProvider.refresh()
}
})
)

// commands
this._register(
vscode.commands.registerCommand('devboxDashboard.refresh', () => {
Expand Down Expand Up @@ -83,12 +80,7 @@ class MyTreeDataProvider implements vscode.TreeDataProvider<MyTreeItem> {

private refreshData(): void {
if (this.treeName === 'devboxDashboard') {
const defaultSSHConfigPath = path.resolve(
os.homedir(),
'.ssh/sealos/devbox_config'
)

parseSSHConfig(defaultSSHConfigPath).then((data) => {
parseSSHConfig(defaultDevboxSSHConfigPath).then((data) => {
this.treeData = data as DevboxListItem[]
this._onDidChangeTreeData.fire(undefined)
})
Expand All @@ -108,9 +100,11 @@ class MyTreeDataProvider implements vscode.TreeDataProvider<MyTreeItem> {
return element
}

// TODO: 根据不同的代理跳转到不同的页面,而且可以进行设置里的配置
create(item: MyTreeItem) {
vscode.commands.executeCommand('devbox.openWebview')
vscode.window.showInformationMessage('create')
vscode.commands.executeCommand('devbox.openExternalLink', [
'https://usw.sailos.io/?openapp=system-devbox',
])
}

async open(item: MyTreeItem) {
Expand Down Expand Up @@ -138,7 +132,7 @@ class MyTreeDataProvider implements vscode.TreeDataProvider<MyTreeItem> {
if (!element) {
// 第一级:显示所有域名
const domains = [
...new Set(this.treeData.map((item) => item.host.split('-')[0])),
...new Set(this.treeData.map((item) => item.host.split('_')[0])),
]
return Promise.resolve(
domains.map(
Expand All @@ -157,10 +151,7 @@ class MyTreeDataProvider implements vscode.TreeDataProvider<MyTreeItem> {
...new Set(
this.treeData
.filter((item) => item.host.startsWith(element.label as string))
.map((item) => {
const parts = item.host.split('-')
return parts.slice(1, 3).join('-')
})
.map((item) => item.host.split('_')[1])
),
]
return Promise.resolve(
Expand All @@ -178,15 +169,15 @@ class MyTreeDataProvider implements vscode.TreeDataProvider<MyTreeItem> {
} else if (!element.devboxName) {
// 第三级:显示指定命名空间下的所有 devbox
const devboxes = this.treeData.filter((item) => {
const parts = item.host.split('-')
const parts = item.host.split('_')
const domain = parts[0]
const namespace = parts.slice(1, 3).join('-')
const namespace = parts[1]
return domain === element.domain && namespace === element.namespace
})
return Promise.resolve(
devboxes.map((devbox) => {
const parts = devbox.host.split('-')
const devboxName = parts.slice(3, -1).join('-')
const parts = devbox.host.split('_')
const devboxName = parts.slice(2).join('_')
const treeItem = new MyTreeItem(
devboxName,
devbox.hostName,
Expand All @@ -195,9 +186,9 @@ class MyTreeDataProvider implements vscode.TreeDataProvider<MyTreeItem> {
element.namespace,
devboxName,
devbox.host,
devbox.remotePath // 添加这个参数
devbox.remotePath
)
treeItem.contextValue = 'devbox' // 确保设置了正确的 contextValue
treeItem.contextValue = 'devbox'
return treeItem
})
)
Expand Down
Loading

0 comments on commit 5e72a83

Please sign in to comment.