diff --git a/extensions/ide/vscode/devbox/.gitignore b/extensions/ide/vscode/devbox/.gitignore index 0db4f32e45f..b9bd7da471b 100644 --- a/extensions/ide/vscode/devbox/.gitignore +++ b/extensions/ide/vscode/devbox/.gitignore @@ -5,4 +5,5 @@ node_modules *.vsix .env -!.vscode/ \ No newline at end of file +!.vscode/ +resources/codicons/ \ No newline at end of file diff --git a/extensions/ide/vscode/devbox/.vscode/launch.json b/extensions/ide/vscode/devbox/.vscode/launch.json index dea90a410ab..5bacc6bd03b 100644 --- a/extensions/ide/vscode/devbox/.vscode/launch.json +++ b/extensions/ide/vscode/devbox/.vscode/launch.json @@ -13,7 +13,7 @@ "outFiles": ["${workspaceFolder}/dist/**/*.js"], "preLaunchTask": "${defaultBuildTask}", "env": { - "NODE_ENV": "development" + // "NODE_ENV": "development" } } ] diff --git a/extensions/ide/vscode/devbox/images/cloud.svg b/extensions/ide/vscode/devbox/images/cloud.svg new file mode 100644 index 00000000000..3a2748703fa --- /dev/null +++ b/extensions/ide/vscode/devbox/images/cloud.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/extensions/ide/vscode/devbox/images/dark/globe.svg b/extensions/ide/vscode/devbox/images/dark/globe.svg deleted file mode 100644 index a622c0bc06c..00000000000 --- a/extensions/ide/vscode/devbox/images/dark/globe.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/extensions/ide/vscode/devbox/images/light/globe.svg b/extensions/ide/vscode/devbox/images/light/globe.svg deleted file mode 100644 index 4699fb54191..00000000000 --- a/extensions/ide/vscode/devbox/images/light/globe.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/extensions/ide/vscode/devbox/images/network.svg b/extensions/ide/vscode/devbox/images/network.svg index c5b58eef6a4..3a2748703fa 100644 --- a/extensions/ide/vscode/devbox/images/network.svg +++ b/extensions/ide/vscode/devbox/images/network.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/extensions/ide/vscode/devbox/l10n/bundle.l10n.zh-CN.json b/extensions/ide/vscode/devbox/l10n/bundle.l10n.zh-CN.json new file mode 100644 index 00000000000..4d9f17e7b08 --- /dev/null +++ b/extensions/ide/vscode/devbox/l10n/bundle.l10n.zh-CN.json @@ -0,0 +1,22 @@ +{ + "Type": "数据库类型", + "Username": "用户名", + "Password": "密码", + "Host": "主机", + "Port": "端口", + "Copy Password": "复制密码", + "Copy Connection String": "复制连接串", + "Connection string copied to clipboard!": "连接串已复制到剪贴板!", + "Please select a region,RegionList are added by your each connection.": "请选择一个可用区,可用区来自于您的每个连接。", + "Only Devbox can be opened.": "只能打开 Devbox。", + "Are you sure to delete?": "确定删除Devbox?", + "This action will only delete the devbox ssh config in the local environment.": "此操作只会删除本地环境中的 Devbox SSH 配置。", + "Delete Devbox failed.": "删除 Devbox 失败。", + "Give us a feedback in our GitHub repository.": "在 GitHub 仓库反馈。", + "Give us a feedback in our help desk system.": "在工单系统反馈。", + "Protocol": "协议", + "Address": "地址", + "Open in Browser": "在浏览器中打开", + "Preview in Editor": "在编辑器中预览", + "Open Database Web Terminal": "打开数据库 Web 终端" +} diff --git a/extensions/ide/vscode/devbox/package-lock.json b/extensions/ide/vscode/devbox/package-lock.json index f33a26b7138..ad0e25aeaee 100644 --- a/extensions/ide/vscode/devbox/package-lock.json +++ b/extensions/ide/vscode/devbox/package-lock.json @@ -1,14 +1,15 @@ { "name": "devbox-aio", - "version": "0.9.14", + "version": "1.2.2024112201", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "devbox-aio", - "version": "0.9.14", - "license": "MIT", + "version": "1.2.2024112201", + "license": "Apache-2.0", "dependencies": { + "@vscode/codicons": "^0.0.36", "axios": "^1.7.5", "execa": "^9.5.1", "ssh-config": "^5.0.0" @@ -603,6 +604,11 @@ "integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==", "dev": true }, + "node_modules/@vscode/codicons": { + "version": "0.0.36", + "resolved": "https://registry.npmmirror.com/@vscode/codicons/-/codicons-0.0.36.tgz", + "integrity": "sha512-wsNOvNMMJ2BY8rC2N2MNBG7yOowV3ov8KlvUE/AiVUlHKTfWsw3OgAOQduX7h0Un6GssKD3aoTVH+TF3DSQwKQ==" + }, "node_modules/@vscode/test-cli": { "version": "0.0.9", "resolved": "https://registry.npmmirror.com/@vscode/test-cli/-/test-cli-0.0.9.tgz", diff --git a/extensions/ide/vscode/devbox/package.json b/extensions/ide/vscode/devbox/package.json index ba36a2378bb..91c93278d4a 100644 --- a/extensions/ide/vscode/devbox/package.json +++ b/extensions/ide/vscode/devbox/package.json @@ -1,27 +1,28 @@ { "name": "devbox-aio", - "displayName": "Devbox", - "description": "help code for cloud devbox in sailos/sealos", - "version": "1.1.9", + "displayName": "%displayName%", + "description": "%description%", + "version": "1.2.2024112203", "keywords": [ "devbox", "remote development", "remote" ], "bugs": { - "url": "https://github.com/mlhiter/devbox/issues" + "url": "https://github.com/labring/sealos/issues" }, "repository": { "type": "git", - "url": "https://github.com/mlhiter/devbox" + "url": "https://github.com/labring/sealos" }, - "homepage": "https://github.com/mlhiter/devbox/blob/master/README.md", + "homepage": "https://github.com/labring/sealos/blob/main/extensions/ide/vscode/devbox/README.md", "publisher": "labring", - "license": "MIT", + "license": "Apache-2.0", "icon": "images/icon.png", "engines": { "vscode": "^1.91.0" }, + "l10n": "./l10n", "categories": [ "Other" ], @@ -37,11 +38,11 @@ "commands": [ { "command": "devbox.connectRemoteSSH", - "title": "Devbox: Connect to Remote SSH" + "title": "%devbox.connectRemoteSSH.title%" }, { "command": "devboxDashboard.refresh", - "title": "Refresh Devbox", + "title": "%devbox.refresh.title%", "icon": { "light": "images/light/refresh.svg", "dark": "images/dark/refresh.svg" @@ -49,7 +50,7 @@ }, { "command": "devboxDashboard.createDevbox", - "title": "Create Devbox", + "title": "%devbox.create.title%", "icon": { "light": "images/light/create.svg", "dark": "images/dark/create.svg" @@ -57,7 +58,7 @@ }, { "command": "devboxDashboard.openDevbox", - "title": "Open Devbox", + "title": "%devbox.open.title%", "icon": { "light": "images/light/open.svg", "dark": "images/dark/open.svg" @@ -65,7 +66,7 @@ }, { "command": "devboxDashboard.deleteDevbox", - "title": "Delete Devbox", + "title": "%devbox.delete.title%", "icon": { "light": "images/light/delete.svg", "dark": "images/dark/delete.svg" @@ -73,11 +74,11 @@ }, { "command": "devbox.openExternalLink", - "title": "Devbox:Open in Browser" + "title": "%devbox.openInBrowser.title%" }, { "command": "devbox.copy", - "title": "Copy Connection String", + "title": "%devbox.copy.title%", "icon": { "light": "images/light/copy.svg", "dark": "images/dark/copy.svg" @@ -85,7 +86,7 @@ }, { "command": "devbox.refreshDatabase", - "title": "Refresh Database", + "title": "%devbox.refreshDatabase.title%", "icon": { "light": "images/light/refresh.svg", "dark": "images/dark/refresh.svg" @@ -93,31 +94,15 @@ }, { "command": "devbox.refreshNetwork", - "title": "Refresh Network", + "title": "%devbox.refreshNetwork.title%", "icon": { "light": "images/light/refresh.svg", "dark": "images/dark/refresh.svg" } }, - { - "command": "devbox.openInIntegratedBrowser", - "title": "Open in Integrated Browser", - "icon": { - "light": "images/light/globe.svg", - "dark": "images/dark/globe.svg" - } - }, - { - "command": "devbox.openInExternalBrowser", - "title": "Open in External Browser", - "icon": { - "light": "images/light/link-external.svg", - "dark": "images/dark/link-external.svg" - } - }, { "command": "devbox.gotoDatabaseWebPage", - "title": "Goto Database Web Page", + "title": "%devbox.gotoDatabaseWebPage.title%", "icon": { "light": "images/light/link-external.svg", "dark": "images/dark/link-external.svg" @@ -128,23 +113,27 @@ "devboxListView": [ { "id": "devboxDashboard", - "name": "My Projects" + "name": "%devbox.myProjects.title%" }, { "id": "devboxFeedback", - "name": "Feedback" + "name": "%devbox.feedback.title%" } ], "networkView": [ { "id": "networkView", - "name": "Network" + "name": "%devbox.network.title%", + "type": "webview", + "when": "remoteName == ssh-remote" } ], "dbView": [ { + "type": "webview", "id": "dbView", - "name": "Database" + "name": "%devbox.database.title%", + "when": "remoteName == ssh-remote" } ] }, @@ -152,19 +141,19 @@ "activitybar": [ { "id": "devboxListView", - "title": "Devbox", + "title": "%devbox.devbox.title%", "icon": "images/explorer.svg" } ], "panel": [ { "id": "networkView", - "title": "Network", + "title": "%devbox.network.title%", "icon": "images/network.svg" }, { "id": "dbView", - "title": "Database", + "title": "%devbox.database.title%", "icon": "images/database.svg" } ] @@ -172,7 +161,7 @@ "viewsWelcome": [ { "view": "devboxDashboard", - "contents": "View and manage your devboxes,Now you can create a new devbox." + "contents": "%devbox.welcome.title%" } ], "menus": { @@ -213,21 +202,6 @@ "command": "devboxDashboard.deleteDevbox", "when": "view == devboxDashboard && viewItem == devbox", "group": "inline@2" - }, - { - "command": "devbox.copy", - "when": "view == dbView && viewItem == database", - "group": "inline" - }, - { - "command": "devbox.openInIntegratedBrowser", - "when": "view == networkView && viewItem == network", - "group": "inline@1" - }, - { - "command": "devbox.openInExternalBrowser", - "when": "view == networkView && viewItem == network", - "group": "inline@2" } ] } @@ -238,11 +212,12 @@ "scripts": { "vscode:prepublish": "npm run package", "compile": "webpack", - "watch": "webpack --watch", - "package": "webpack --mode production --devtool hidden-source-map", + "watch": "npm run copy-codicons && webpack --watch", + "package": "npm run copy-codicons && webpack --mode production --devtool hidden-source-map", "compile-tests": "tsc -p . --outDir out", "watch-tests": "tsc -p . -w --outDir out", "pretest": "npm run compile-tests && npm run compile && npm run lint", + "copy-codicons": "mkdir -p resources/codicons && cp -r node_modules/@vscode/codicons/dist/* resources/codicons/", "lint": "eslint src", "test": "vscode-test" }, @@ -261,6 +236,7 @@ "webpack-cli": "^5.1.4" }, "dependencies": { + "@vscode/codicons": "^0.0.36", "axios": "^1.7.5", "execa": "^9.5.1", "ssh-config": "^5.0.0" diff --git a/extensions/ide/vscode/devbox/package.nls.json b/extensions/ide/vscode/devbox/package.nls.json new file mode 100644 index 00000000000..e1ef2266b04 --- /dev/null +++ b/extensions/ide/vscode/devbox/package.nls.json @@ -0,0 +1,20 @@ +{ + "displayName": "Devbox", + "description": "help code for cloud devbox in sailos/sealos", + "devbox.connectRemoteSSH.title": "Devbox: Connect to Remote SSH", + "devbox.refresh.title": "Devbox: Refresh", + "devbox.create.title": "Devbox: Create new Devbox", + "devbox.open.title": "Devbox: Open Devbox", + "devbox.delete.title": "Devbox: Delete Devbox", + "devbox.openInBrowser.title": "Devbox: Open in Browser", + "devbox.copy.title": "Devbox: Copy", + "devbox.refreshDatabase.title": "Devbox: Refresh Database", + "devbox.refreshNetwork.title": "Devbox: Refresh Network", + "devbox.gotoDatabaseWebPage.title": "Devbox: Open Database Web Page", + "devbox.myProjects.title": "My Devboxes", + "devbox.feedback.title": "Feedback", + "devbox.network.title": "Network", + "devbox.database.title": "Database", + "devbox.devbox.title": "Devbox", + "devbox.welcome.title": "No Devbox yet, please create a new devbox.\n [Create Devbox](command:devboxDashboard.createDevbox)\n To learn more about how to use Devbox, please visit [Devbox Documentation](https://sailos.io/docs/quick-start)." +} diff --git a/extensions/ide/vscode/devbox/package.nls.zh-CN.json b/extensions/ide/vscode/devbox/package.nls.zh-CN.json new file mode 100644 index 00000000000..cf9cd353bc3 --- /dev/null +++ b/extensions/ide/vscode/devbox/package.nls.zh-CN.json @@ -0,0 +1,20 @@ +{ + "displayName": "Devbox", + "description": "用于 sailos/sealos 中云Devbox的辅助工具", + "devbox.connectRemoteSSH.title": "Devbox: 连接远程 SSH", + "devbox.refresh.title": "Devbox: 刷新", + "devbox.create.title": "Devbox: 创建", + "devbox.open.title": "Devbox: 打开", + "devbox.delete.title": "Devbox: 删除", + "devbox.openInBrowser.title": "Devbox: 在浏览器中打开", + "devbox.copy.title": "Devbox: 复制", + "devbox.refreshDatabase.title": "Devbox: 刷新数据库", + "devbox.refreshNetwork.title": "Devbox: 刷新网络", + "devbox.gotoDatabaseWebPage.title": "Devbox: 打开数据库网页", + "devbox.myProjects.title": "我的项目", + "devbox.feedback.title": "反馈", + "devbox.network.title": "网络", + "devbox.database.title": "数据库", + "devbox.devbox.title": "Devbox", + "devbox.welcome.title": "还没有Devbox,请创建一个新的Devbox。\n [创建Devbox](command:devboxDashboard.createDevbox)\n 要了解更多关于如何使用Devbox的信息,请访问[Devbox文档](https://sailos.io/docs/quick-start)。" +} diff --git a/extensions/ide/vscode/devbox/src/commands/remoteConnector.ts b/extensions/ide/vscode/devbox/src/commands/remoteConnector.ts index 837a14b11bd..da828fd17bc 100644 --- a/extensions/ide/vscode/devbox/src/commands/remoteConnector.ts +++ b/extensions/ide/vscode/devbox/src/commands/remoteConnector.ts @@ -13,6 +13,7 @@ import { import { convertSSHConfigToVersion2 } from '../utils/sshConfig' import { ensureFileAccessPermission, ensureFileExists } from '../utils/file' import { GlobalStateManager } from '../utils/globalStateManager' +import { Logger } from '../common/logger' export class RemoteSSHConnector extends Disposable { constructor(context: vscode.ExtensionContext) { @@ -27,24 +28,34 @@ export class RemoteSSHConnector extends Disposable { } private replaceHomePathInConfig(content: string): string { - const includePattern = /Include ~\/.ssh\/sealos\/devbox_config/ + const includePattern = new RegExp( + `Include ${os.homedir()}/.ssh/sealos/devbox_config`, + 'g' + ) const includePattern2 = new RegExp( - `Include ${os.homedir()}/.ssh/sealos/devbox_config` + `Include "${os.homedir()}/.ssh/sealos/devbox_config"`, + 'g' ) - const includeLine = `Include "${os.homedir()}/.ssh/sealos/devbox_config"` + + const includeLine = `Include ~/.ssh/sealos/devbox_config` if (includePattern.test(content)) { - return content.replace(includePattern, includeLine) - } else if (includePattern2.test(content)) { - return content.replace(includePattern2, includeLine) - } else if (content.includes(includeLine)) { + return content.replace(includePattern, '') + } + + if (includePattern2.test(content)) { + return content.replace(includePattern2, '') + } + + if (content.includes(includeLine)) { return content - } else { - return `${includeLine}\n${content}` } + + return `${includeLine}\n${content}` } private sshConfigPreProcess() { + Logger.info('SSH config pre-processing') // 1. ensure .ssh/config exists ensureFileExists(defaultSSHConfigPath, '.ssh') // 2. ensure .ssh/sealos/devbox_config exists @@ -71,6 +82,8 @@ export class RemoteSSHConnector extends Disposable { } // 4. ensure sshConfig from version1 to version2 convertSSHConfigToVersion2(defaultDevboxSSHConfigPath) + + Logger.info('SSH config pre-processing completed') } private handleDefaultSSHConfig() { @@ -88,6 +101,8 @@ export class RemoteSSHConnector extends Disposable { sshHostLabel: string workingDir: string }) { + Logger.info(`Connecting to remote SSH: ${args.sshHostLabel}`) + this.ensureRemoteSSHExtInstalled() const { sshDomain, sshPort, base64PrivateKey, sshHostLabel, workingDir } = @@ -118,6 +133,8 @@ export class RemoteSSHConnector extends Disposable { this.sshConfigPreProcess() try { + Logger.info('Writing SSH config to .ssh/sealos/devbox_config') + const existingDevboxConfigLines = fs .readFileSync(defaultDevboxSSHConfigPath, 'utf8') .split('\n') @@ -158,7 +175,10 @@ export class RemoteSSHConnector extends Disposable { // 5. write new ssh config to .ssh/sealos/devbox_config fs.appendFileSync(defaultDevboxSSHConfigPath, `\n${sshConfigString}\n`) + + Logger.info('SSH config written to .ssh/sealos/devbox_config') } catch (error) { + Logger.error(`Failed to write SSH configuration: ${error}`) vscode.window.showErrorMessage( `Failed to write SSH configuration: ${error}` ) @@ -166,15 +186,20 @@ export class RemoteSSHConnector extends Disposable { // 6. create sealos privateKey file in .ssh/sealos try { + Logger.info('Creating sealos privateKey file in .ssh/sealos') const sshKeyPath = defaultSSHKeyPath + `/${sshHostLabel}` fs.writeFileSync(sshKeyPath, normalPrivateKey) ensureFileAccessPermission(sshKeyPath) + Logger.info('Sealos privateKey file created in .ssh/sealos') } catch (error) { + Logger.error(`Failed to write SSH private key: ${error}`) vscode.window.showErrorMessage( `Failed to write SSH private key: ${error}` ) } + Logger.info('Opening Devbox in VSCode') + await vscode.commands.executeCommand( 'vscode.openFolder', vscode.Uri.parse( @@ -185,6 +210,8 @@ export class RemoteSSHConnector extends Disposable { } ) + Logger.info('Devbox opened in VSCode') + // refresh devboxList await vscode.commands.executeCommand('devboxDashboard.refresh') } @@ -232,6 +259,8 @@ export class RemoteSSHConnector extends Disposable { 'ms-vscode-remote.remote-ssh' ) + Logger.info('"ms-vscode-remote.remote-ssh" extension is installed') + return true } } diff --git a/extensions/ide/vscode/devbox/src/common/logger.ts b/extensions/ide/vscode/devbox/src/common/logger.ts new file mode 100644 index 00000000000..0419b436767 --- /dev/null +++ b/extensions/ide/vscode/devbox/src/common/logger.ts @@ -0,0 +1,31 @@ +import * as vscode from 'vscode' + +export class Logger { + private static outputChannel: vscode.OutputChannel + + static init(context: vscode.ExtensionContext) { + this.outputChannel = vscode.window.createOutputChannel('Devbox') + } + + static info(message: string) { + const log = `[INFO] ${new Date().toISOString()} ${message}` + this.outputChannel.appendLine(log) + } + + static error(message: string, error?: any) { + const errorMessage = error ? `${message}: ${error.toString()}` : message + const log = `[ERROR] ${new Date().toISOString()} ${errorMessage}` + this.outputChannel.appendLine(log) + } + + static debug(message: string) { + if (process.env.NODE_ENV === 'development') { + const log = `[DEBUG] ${new Date().toISOString()} ${message}` + this.outputChannel.appendLine(log) + } + } + + static show() { + this.outputChannel.show() + } +} diff --git a/extensions/ide/vscode/devbox/src/extension.ts b/extensions/ide/vscode/devbox/src/extension.ts index 1254c46ae1e..d512c644974 100644 --- a/extensions/ide/vscode/devbox/src/extension.ts +++ b/extensions/ide/vscode/devbox/src/extension.ts @@ -1,6 +1,7 @@ import * as vscode from 'vscode' import { updateBaseUrl } from './api' +import { Logger } from './common/logger' import { UriHandler } from './utils/handleUri' import { isDevelopment } from './constant/api' import { ToolCommands } from './commands/tools' @@ -11,6 +12,9 @@ import { DBViewProvider } from './providers/DBViewProvider' import { GlobalStateManager } from './utils/globalStateManager' export async function activate(context: vscode.ExtensionContext) { + // Logger + Logger.init(context) + // tools const tools = new ToolCommands(context) context.subscriptions.push(tools) @@ -52,6 +56,7 @@ export async function activate(context: vscode.ExtensionContext) { handleUri: (uri) => uriHandler.handle(uri), }) ) + console.log('Your extension "devbox" is now active!') } export function deactivate() {} diff --git a/extensions/ide/vscode/devbox/src/providers/DBViewProvider.ts b/extensions/ide/vscode/devbox/src/providers/DBViewProvider.ts index d68565ff2ad..b1c99f5a7ca 100644 --- a/extensions/ide/vscode/devbox/src/providers/DBViewProvider.ts +++ b/extensions/ide/vscode/devbox/src/providers/DBViewProvider.ts @@ -1,49 +1,79 @@ import * as vscode from 'vscode' + import { getDBList, DBResponse } from '../api/db' import { Disposable } from '../common/dispose' import { GlobalStateManager } from '../utils/globalStateManager' +import { Logger } from '../common/logger' + +enum DBTypeEnum { + postgresql = 'postgresql', + mongodb = 'mongodb', + mysql = 'mysql', + redis = 'redis', + kafka = 'kafka', + qdrant = 'qdrant', + nebula = 'nebula', + weaviate = 'weaviate', + milvus = 'milvus', +} interface Database { - dbType: string + dbType: DBTypeEnum username: string password: string host: string port: number connection: string } +interface Messages { + columnDBType: string + columnUsername: string + columnPassword: string + columnHost: string + columnPort: string + copyPassword: string + copyConnection: string + connectionStringCopied: string + openWebTerminal: string +} + +const messages: Messages = { + columnDBType: vscode.l10n.t('Type'), + columnUsername: vscode.l10n.t('Username'), + columnPassword: vscode.l10n.t('Password'), + columnHost: vscode.l10n.t('Host'), + columnPort: vscode.l10n.t('Port'), + copyPassword: vscode.l10n.t('Copy Password'), + copyConnection: vscode.l10n.t('Copy Connection String'), + connectionStringCopied: vscode.l10n.t( + 'Connection string copied to clipboard!' + ), + openWebTerminal: vscode.l10n.t('Open Database Web Terminal'), +} -export class DBViewProvider extends Disposable { +export class DBViewProvider + extends Disposable + implements vscode.WebviewViewProvider +{ + private _view?: vscode.WebviewView + private _extensionUri: vscode.Uri constructor(context: vscode.ExtensionContext) { super() + Logger.info('Initializing DBViewProvider') + this._extensionUri = context.extensionUri if (context.extension.extensionKind === vscode.ExtensionKind.UI) { // view - const dbTreeDataProvider = new MyDbTreeDataProvider() - const dbView = vscode.window.createTreeView('dbView', { - treeDataProvider: dbTreeDataProvider, - }) - this._register(dbView) - - // commands - this._register( - vscode.workspace.onDidChangeWorkspaceFolders(() => { - dbTreeDataProvider.refresh() - }) - ) - this._register( - dbView.onDidChangeVisibility(() => { - if (dbView.visible) { - dbTreeDataProvider.refresh() - } + context.subscriptions.push( + vscode.window.registerWebviewViewProvider('dbView', this, { + webviewOptions: { + retainContextWhenHidden: true, + }, }) ) + // commands this._register( vscode.commands.registerCommand('devbox.refreshDatabase', () => { - dbTreeDataProvider.refresh() - }) - ) - this._register( - vscode.commands.registerCommand('devbox.copy', (item: DatabaseItem) => { - dbTreeDataProvider.copyConnectionString(item) + this.refreshDatabases() }) ) let targetUrl = '' @@ -64,26 +94,67 @@ export class DBViewProvider extends Disposable { } } } -} -class MyDbTreeDataProvider implements vscode.TreeDataProvider { - private _onDidChangeTreeData: vscode.EventEmitter = - new vscode.EventEmitter() - readonly onDidChangeTreeData: vscode.Event = - this._onDidChangeTreeData.event - constructor() { - this.init() - } - private databases: Database[] = [] + public async resolveWebviewView( + webviewView: vscode.WebviewView, + context: vscode.WebviewViewResolveContext, + token: vscode.CancellationToken + ) { + this._view = webviewView + + webviewView.webview.options = { + enableScripts: true, + } - private async init() { - this.refresh() + webviewView.webview.onDidReceiveMessage(async (message) => { + switch (message.command) { + case 'refresh': + await this.refreshDatabases() + break + case 'copy': + await this.copyConnectionString(message.connection) + break + case 'openWebTerminal': + await this.openWebTerminal(message.dbInfo) + break + } + }) + + await this.refreshDatabases() + } + private async openWebTerminal(dbInfo: Database) { + console.log('dbInfo', dbInfo) + const commandMap = { + postgresql: `psql '${dbInfo.connection}'`, + mongodb: `mongosh '${dbInfo.connection}'`, + mysql: `mysql -h ${dbInfo.host} -P ${dbInfo.port} -u ${dbInfo.username} -p${dbInfo.password}`, + redis: `redis-cli -u redis://${dbInfo.username}:${dbInfo.password}@${dbInfo.host}:${dbInfo.port}`, + kafka: ``, + qdrant: ``, + nebula: ``, + weaviate: ``, + milvus: ``, + } + const targetCommand = encodeURIComponent(commandMap[dbInfo.dbType]) + let targetUrl = '' + const workspaceFolders = vscode.workspace.workspaceFolders + if (workspaceFolders && workspaceFolders.length > 0) { + const workspaceFolder = workspaceFolders[0] + const remoteUri = workspaceFolder.uri.authority + const devboxId = remoteUri.replace(/^ssh-remote\+/, '') // devbox = sshHostLabel + const region = GlobalStateManager.getRegion(devboxId) + targetUrl = `http://${region}?openapp=system-terminal?defaultCommand=${targetCommand}` + vscode.commands.executeCommand('devbox.openExternalLink', [targetUrl]) + } } - async refresh(): Promise { + private async refreshDatabases() { + if (!this._view) { + return + } + const dbList = await getDBList() - this.databases = dbList.map((db: DBResponse) => ({ - dbName: db.dbName, + const databases = dbList.map((db: DBResponse) => ({ dbType: db.dbType, username: db.username, password: db.password, @@ -91,73 +162,165 @@ class MyDbTreeDataProvider implements vscode.TreeDataProvider { port: db.port, connection: db.connection, })) - this._onDidChangeTreeData.fire(undefined) - } - getTreeItem(element: DatabaseItem): vscode.TreeItem { - return element - } - copyConnectionString(item: DatabaseItem) { - if (item.connectionString && item.contextValue === 'database') { - vscode.env.clipboard.writeText(item.connectionString) - vscode.window.showInformationMessage( - 'Connection string is copied to clipboard!' - ) - } + this._view.webview.html = this.getWebviewContent(databases, messages) } - async getChildren(element?: DatabaseItem): Promise { - if (!element) { - const items: DatabaseItem[] = [] - const remoteName = vscode.env.remoteName - - if (!remoteName) { - return [ - new DatabaseItem( - 'Not connected to the remote environment', - 'no-remote' - ), - ] - } + private async copyConnectionString(connection: string) { + await vscode.env.clipboard.writeText(connection) + vscode.window.showInformationMessage(messages.connectionStringCopied) + } - items.push( - new DatabaseItem( - `${'DBType'.padEnd(15)}${'Username'.padEnd(50)}${'Host'.padEnd( - 66 - )}${'Port'.padEnd(40)}Connection`, - 'header' - ) + private getWebviewContent(databases: Database[], messages: Messages) { + const codiconsUri = this._view?.webview.asWebviewUri( + vscode.Uri.joinPath( + this._extensionUri, + 'resources', + 'codicons', + 'codicon.css' ) + ) - this.databases.forEach((database) => { - const label = `${database.dbType.padEnd(15)} ${database.username.padEnd( - 17 - )}${database.host.padEnd(65)} ${database.port - .toString() - .padEnd(34)} ${'*'.repeat(20)}` - items.push( - new DatabaseItem( - label, - 'database', - database.connection, - database.password - ) - ) - }) - - return items - } - return [] - } -} + return ` + + + + + + + + + + + + + + + + + + + + ${databases + .map( + (db) => ` + + + + + + + + -class DatabaseItem extends vscode.TreeItem { - constructor( - public override readonly label: string, - public override readonly contextValue: string, - public readonly connectionString?: string, - public readonly password?: string - ) { - super(label, vscode.TreeItemCollapsibleState.None) + + ` + ) + .join('')} + +
${messages.columnDBType}${messages.columnUsername}${messages.columnPassword}${messages.columnHost}${messages.columnPort}
${db.dbType}${db.username} + ${'*'.repeat(8)} + + + + ${db.host}${db.port} + + +
+ + + + ` } } diff --git a/extensions/ide/vscode/devbox/src/providers/DevboxListViewProvider.ts b/extensions/ide/vscode/devbox/src/providers/DevboxListViewProvider.ts index b2578f0380e..3600a9851ee 100644 --- a/extensions/ide/vscode/devbox/src/providers/DevboxListViewProvider.ts +++ b/extensions/ide/vscode/devbox/src/providers/DevboxListViewProvider.ts @@ -1,17 +1,37 @@ -import * as vscode from 'vscode' import fs from 'fs' +import * as vscode from 'vscode' +import { Logger } from '../common/logger' import { parseSSHConfig } from '../api/ssh' import { Disposable } from '../common/dispose' import { DevboxListItem } from '../types/devbox' import { getDevboxDetail } from '../api/devbox' -import { GlobalStateManager } from '../utils/globalStateManager' import { convertSSHConfigToVersion2 } from '../utils/sshConfig' +import { GlobalStateManager } from '../utils/globalStateManager' import { defaultDevboxSSHConfigPath, defaultSSHKeyPath } from '../constant/file' +const messages = { + pleaseSelectARegion: vscode.l10n.t( + 'Please select a region,RegionList are added by your each connection.' + ), + onlyDevboxCanBeOpened: vscode.l10n.t('Only Devbox can be opened.'), + areYouSureToDelete: vscode.l10n.t('Are you sure to delete?'), + deleteLocalConfigOnly: vscode.l10n.t( + 'This action will only delete the devbox ssh config in the local environment.' + ), + deleteDevboxFailed: vscode.l10n.t('Delete Devbox failed.'), + feedbackInGitHub: vscode.l10n.t( + 'Give us a feedback in our GitHub repository.' + ), + feedbackInHelpDesk: vscode.l10n.t( + 'Give us a feedback in our help desk system.' + ), +} + export class DevboxListViewProvider extends Disposable { constructor(context: vscode.ExtensionContext) { super() + Logger.info('Initializing DevboxListViewProvider') if (context.extension.extensionKind === vscode.ExtensionKind.UI) { // view const projectTreeDataProvider = new ProjectTreeDataProvider() @@ -83,6 +103,7 @@ class ProjectTreeDataProvider private treeData: DevboxListItem[] = [] constructor() { + convertSSHConfigToVersion2(defaultDevboxSSHConfigPath) this.refreshData() setInterval(() => { this.refresh() @@ -94,7 +115,6 @@ class ProjectTreeDataProvider } private async refreshData(): Promise { - convertSSHConfigToVersion2(defaultDevboxSSHConfigPath) const data = (await parseSSHConfig( defaultDevboxSSHConfigPath )) as DevboxListItem[] @@ -129,18 +149,18 @@ class ProjectTreeDataProvider } } catch (error) { console.error(`get devbox detail failed: ${error}`) - if ( - error.toString().includes('500:secrets') && - error.toString().includes('not found') - ) { - const hostParts = item.host.split('_') - const devboxName = hostParts.slice(2).join('_') - if (error.toString().includes(devboxName)) { - await this.delete(item.host, devboxName, true) - - return - } - } + // if ( + // error.toString().includes('500:secrets') && + // error.toString().includes('not found') + // ) { + // const hostParts = item.host.split('_') + // const devboxName = hostParts.slice(2).join('_') + // if (error.toString().includes(devboxName)) { + // await this.delete(item.host, devboxName, true) + + // return + // } + // } item.iconPath = new vscode.ThemeIcon('warning') } }) @@ -157,14 +177,12 @@ class ProjectTreeDataProvider const regions = GlobalStateManager.getApiRegionList() const selected = await vscode.window.showQuickPick(regions, { - placeHolder: - 'Please select a region.RegionList are added by your each connection', + placeHolder: messages.pleaseSelectARegion, }) - if (selected) { const targetUrl = selected vscode.commands.executeCommand('devbox.openExternalLink', [ - `${targetUrl}/?openapp=system-devbox?${encodeURIComponent( + `https://${targetUrl}/?openapp=system-devbox?${encodeURIComponent( 'page=create' )}`, ]) @@ -173,7 +191,7 @@ class ProjectTreeDataProvider async open(item: ProjectTreeItem) { if (item.contextValue !== 'devbox') { - vscode.window.showInformationMessage('只能打开 Devbox 项目') + vscode.window.showInformationMessage(messages.onlyDevboxCanBeOpened) return } @@ -195,7 +213,7 @@ class ProjectTreeDataProvider ) { if (!isDeletedByWeb) { const result = await vscode.window.showWarningMessage( - `Are you sure to delete ${devboxName}?\n(This action will only delete the devbox ssh config in the local environment.)`, + `${messages.areYouSureToDelete} ${devboxName}?\n(${messages.deleteLocalConfigOnly})`, { modal: true }, 'Yes', 'No' @@ -276,7 +294,9 @@ class ProjectTreeDataProvider this.refresh() } catch (error) { - vscode.window.showErrorMessage(`Delete devbox failed: ${error.message}`) + vscode.window.showErrorMessage( + `${messages.deleteDevboxFailed}: ${error.message}` + ) } } @@ -373,7 +393,7 @@ class FeedbackTreeDataProvider getChildren(element?: FeedbackTreeItem): Thenable { return Promise.resolve([ new FeedbackTreeItem( - 'Give us a feedback in our GitHub repository', + messages.feedbackInGitHub, vscode.TreeItemCollapsibleState.None, new vscode.ThemeIcon('github'), { @@ -383,7 +403,7 @@ class FeedbackTreeDataProvider } ), new FeedbackTreeItem( - 'Give us a feedback in our help desk system', + messages.feedbackInHelpDesk, vscode.TreeItemCollapsibleState.None, new vscode.ThemeIcon('comment'), { diff --git a/extensions/ide/vscode/devbox/src/providers/NetworkViewProvider.ts b/extensions/ide/vscode/devbox/src/providers/NetworkViewProvider.ts index 7d6bd0f2573..23988cf9b4c 100644 --- a/extensions/ide/vscode/devbox/src/providers/NetworkViewProvider.ts +++ b/extensions/ide/vscode/devbox/src/providers/NetworkViewProvider.ts @@ -1,6 +1,7 @@ import * as vscode from 'vscode' -import { getNetworkList, NetworkResponse } from '../api/network' +import { Logger } from '../common/logger' import { Disposable } from '../common/dispose' +import { getNetworkList, NetworkResponse } from '../api/network' interface Network { address: string @@ -8,144 +9,207 @@ interface Network { protocol: string } -export class NetworkViewProvider extends Disposable { +const messages = { + port: vscode.l10n.t('Port'), + protocol: vscode.l10n.t('Protocol'), + address: vscode.l10n.t('Address'), + openInBrowser: vscode.l10n.t('Open in Browser'), + previewInEditor: vscode.l10n.t('Preview in Editor'), +} + +export class NetworkViewProvider + extends Disposable + implements vscode.WebviewViewProvider +{ + private _view?: vscode.WebviewView + private _extensionUri: vscode.Uri + constructor(context: vscode.ExtensionContext) { super() + Logger.info('Initializing NetworkViewProvider') + this._extensionUri = context.extensionUri if (context.extension.extensionKind === vscode.ExtensionKind.UI) { - const networkTreeDataProvider = new MyNetworkTreeDataProvider() - const networkView = vscode.window.createTreeView('networkView', { - treeDataProvider: networkTreeDataProvider, - }) - this._register(networkView) - - this._register( - vscode.workspace.onDidChangeWorkspaceFolders(() => { - networkTreeDataProvider.refresh() - }) - ) - this._register( - networkView.onDidChangeVisibility(() => { - if (networkView.visible) { - networkTreeDataProvider.refresh() - } + context.subscriptions.push( + vscode.window.registerWebviewViewProvider('networkView', this, { + webviewOptions: { + retainContextWhenHidden: true, + }, }) ) this._register( vscode.commands.registerCommand('devbox.refreshNetwork', () => { - networkTreeDataProvider.refresh() + this.refreshNetworks() }) ) - - this._register( - vscode.commands.registerCommand( - 'devbox.openInIntegratedBrowser', - (item) => networkTreeDataProvider.openInIntegratedBrowser(item) - ) - ) - this._register( - vscode.commands.registerCommand( - 'devbox.openInExternalBrowser', - (item) => networkTreeDataProvider.openInExternalBrowser(item) - ) - ) } } -} -class MyNetworkTreeDataProvider - implements vscode.TreeDataProvider -{ - private _onDidChangeTreeData: vscode.EventEmitter = - new vscode.EventEmitter() - readonly onDidChangeTreeData: vscode.Event = - this._onDidChangeTreeData.event - private networks: Network[] = [] + public async resolveWebviewView( + webviewView: vscode.WebviewView, + context: vscode.WebviewViewResolveContext, + token: vscode.CancellationToken + ) { + this._view = webviewView - constructor() { - this.init() - } + webviewView.webview.options = { + enableScripts: true, + } - private async init() { - this.refresh() - } - openInIntegratedBrowser(item: NetworkItem) { - vscode.commands.executeCommand( - 'simpleBrowser.show', - `https://${item.address.split(/\s+/).pop()}` - ) - } + webviewView.webview.onDidReceiveMessage(async (message) => { + switch (message.command) { + case 'refresh': + await this.refreshNetworks() + break + case 'openExternal': + vscode.commands.executeCommand('devbox.openExternalLink', message.url) + break + case 'openIntegrated': + vscode.commands.executeCommand('simpleBrowser.show', message.url) + break + } + }) - openInExternalBrowser(item: NetworkItem) { - vscode.commands.executeCommand( - 'devbox.openExternalLink', - `https://${item.address.split(/\s+/).pop()}` - ) + await this.refreshNetworks() } - async refresh(): Promise { + private async refreshNetworks() { + if (!this._view) { + return + } + const networks = await getNetworkList() - this.networks = networks.map((network: NetworkResponse) => ({ + const networkItems = networks.map((network: NetworkResponse) => ({ address: network.address, port: network.port, protocol: network.protocol, })) - this._onDidChangeTreeData.fire(undefined) - } - getTreeItem(element: NetworkItem): vscode.TreeItem { - return element + this._view.webview.html = this.getWebviewContent(networkItems) } - async getChildren(element?: NetworkItem): Promise { - if (!element) { - const items: NetworkItem[] = [] - const remoteName = vscode.env.remoteName - - if (!remoteName) { - return [ - new NetworkItem( - 'Not connected to the remote environment', - 'no-remote', - '' - ), - ] - } - - items.push( - new NetworkItem( - `${'Port'.padEnd(40)}${'Protocol'.padEnd(60)}Address`, - 'header', - '' - ) + private getWebviewContent(networks: Network[]) { + const codiconsUri = this._view?.webview.asWebviewUri( + vscode.Uri.joinPath( + this._extensionUri, + 'resources', + 'codicons', + 'codicon.css' ) + ) - this.networks.forEach((network) => { - const label = `${network.port - .toString() - .padEnd(38)}${network.protocol.padEnd(60)}${network.address}` - items.push(new NetworkItem(label, 'network', network.address)) - }) - - return items - } - return [] - } -} - -class NetworkItem extends vscode.TreeItem { - constructor( - public override readonly label: string, - public override readonly contextValue: string, - public address: string - ) { - super(label, vscode.TreeItemCollapsibleState.None) - - if (contextValue === 'network') { - this.command = { - command: 'devbox.openExternalLink', - title: 'Devbox: Open in Browser', - arguments: [`https://${label.split(/\s+/).pop() || ''}`], - } - } + return ` + + + + + + + + + + + + + + + + + + ${networks + .map( + (network) => ` + + + + + + + + ` + ) + .join('')} + +
${messages.port}${messages.protocol}${messages.address}
${network.port}${network.protocol}${network.address} + + +
+ + + + ` } } diff --git a/extensions/ide/vscode/devbox/src/utils/file.ts b/extensions/ide/vscode/devbox/src/utils/file.ts index 796cb89a94d..b6b888b19c5 100644 --- a/extensions/ide/vscode/devbox/src/utils/file.ts +++ b/extensions/ide/vscode/devbox/src/utils/file.ts @@ -2,25 +2,28 @@ import * as os from 'os' import path from 'path' import * as fs from 'fs' import { execa } from 'execa' +import { Logger } from '../common/logger' // File access permission modification export const ensureFileAccessPermission = async (path: string) => { + Logger.info(`Ensuring file access permission for ${path}`) if (os.platform() === 'win32') { try { const username = os.userInfo().username if (!username) { throw new Error('can not get username') } - await execa('icacls', [path, '/inheritance:r']) - await execa('icacls', [path, '/grant:r', `${username}:F`]) - await execa('icacls', [path, '/remove:g', 'everyone']) + // await execa('icacls', [path, '/inheritance:r']) + // await execa('icacls', [path, '/grant:r', `${username}:F`]) + // await execa('icacls', [path, '/remove:g', 'everyone']) } catch (error) { - console.error('set file access permission failed:', error) - throw new Error(`set file access permission failed: ${error.message}`) + Logger.error(`Failed to set file access permission: ${error}`) } } else { await execa('chmod', ['600', path]) } + + Logger.info(`File access permission set for ${path}`) } export function ensureFileExists(filePath: string, parentDir: string) { diff --git a/extensions/ide/vscode/devbox/src/utils/handleUri.ts b/extensions/ide/vscode/devbox/src/utils/handleUri.ts index 077e38e615d..f053a6cb4fc 100644 --- a/extensions/ide/vscode/devbox/src/utils/handleUri.ts +++ b/extensions/ide/vscode/devbox/src/utils/handleUri.ts @@ -1,18 +1,27 @@ import * as vscode from 'vscode' import { GlobalStateManager } from './globalStateManager' +import { Logger } from '../common/logger' export class UriHandler { constructor() {} public handle(uri: vscode.Uri): void { + Logger.info(`Handling URI: ${uri.toString()}`) if ( uri.scheme !== 'vscode' && uri.scheme !== 'cursor' && - uri.scheme !== 'vscode-insiders' + uri.scheme !== 'vscode-insiders' && + uri.scheme !== 'windsurf' ) { return } + if (uri.scheme === 'cursor') { + vscode.window.showInformationMessage( + "Cursor's Devbox is often not the latest. If there are any issues, please manually install the [plugin](https://marketplace.visualstudio.com/items?itemName=labring.devbox-aio&ssr=false#overview) referenced this [URI](https://www.cursor.com/how-to-install-extension)." + ) + } + const queryParams = new URLSearchParams(uri.query) const params = this.extractParams(queryParams) diff --git a/extensions/ide/vscode/devbox/src/utils/remoteSSHConfig.ts b/extensions/ide/vscode/devbox/src/utils/remoteSSHConfig.ts index 6bedaa95877..1d3741908ba 100644 --- a/extensions/ide/vscode/devbox/src/utils/remoteSSHConfig.ts +++ b/extensions/ide/vscode/devbox/src/utils/remoteSSHConfig.ts @@ -1,7 +1,10 @@ import * as vscode from 'vscode' +import { Logger } from '../common/logger' // update Remote-SSH config export const modifiedRemoteSSHConfig = async (sshHostLabel: string) => { + Logger.info(`Modifying Remote-SSH config for ${sshHostLabel}`) + const existingSSHHostPlatforms = vscode.workspace .getConfiguration('remote.SSH') .get<{ [host: string]: string }>('remotePlatform', {}) @@ -37,4 +40,6 @@ export const modifiedRemoteSSHConfig = async (sshHostLabel: string) => { await vscode.workspace .getConfiguration('remote.SSH') .update('useLocalServer', true, vscode.ConfigurationTarget.Global) + + Logger.info(`Modified Remote-SSH config for ${sshHostLabel}`) } diff --git a/extensions/ide/vscode/devbox/src/utils/sshConfig.ts b/extensions/ide/vscode/devbox/src/utils/sshConfig.ts index fd067497797..f964a40a05e 100644 --- a/extensions/ide/vscode/devbox/src/utils/sshConfig.ts +++ b/extensions/ide/vscode/devbox/src/utils/sshConfig.ts @@ -1,5 +1,6 @@ import * as fs from 'fs' import { GlobalStateManager } from './globalStateManager' +import { Logger } from '../common/logger' // 将老版本的 ssh 配置改成新版本的 ssh 配置 // # WorkingDir: /home/sealos/project @@ -27,6 +28,13 @@ export function convertSSHConfigToVersion2(filePath: string) { const data = fs.readFileSync(filePath, 'utf8') + if (!data.includes('# WorkingDir:')) { + Logger.info('SSH config is already in the latest version2.') + return + } + + Logger.info('Converting SSH config to the latest version2.') + const lines = data.split('\n') let currentWorkDir: any = null let formattedHostName = '' @@ -87,4 +95,6 @@ export function convertSSHConfigToVersion2(filePath: string) { } result = result.trim() fs.writeFileSync(filePath, result, { encoding: 'utf8', flag: 'w' }) + + Logger.info('SSH config converted to the latest version2.') } diff --git a/frontend/providers/devbox/app/[lang]/(platform)/(home)/page.tsx b/frontend/providers/devbox/app/[lang]/(platform)/(home)/page.tsx index 20f4c2d66e3..5e852d40690 100644 --- a/frontend/providers/devbox/app/[lang]/(platform)/(home)/page.tsx +++ b/frontend/providers/devbox/app/[lang]/(platform)/(home)/page.tsx @@ -104,7 +104,7 @@ const EmptyPage = () => { ) useEffect(() => { - router.prefetch('/devbox/detail') + // router.prefetch('/devbox/detail') router.prefetch('/devbox/create') }, [router]) diff --git a/frontend/providers/devbox/app/[lang]/(platform)/devbox/create/components/Form.tsx b/frontend/providers/devbox/app/[lang]/(platform)/devbox/create/components/Form.tsx index 178b279f821..15142e8adcf 100644 --- a/frontend/providers/devbox/app/[lang]/(platform)/devbox/create/components/Form.tsx +++ b/frontend/providers/devbox/app/[lang]/(platform)/devbox/create/components/Form.tsx @@ -234,7 +234,7 @@ const Form = ({ h={'20px'} color={activeNav === item.id ? 'myGray.400' : 'grayModern.600'} /> - {item.label} + {item?.label} ))} @@ -386,7 +386,7 @@ const Form = ({ }} mt={'4px'} textAlign={'center'}> - {item.label} + {item?.label} ) @@ -467,7 +467,7 @@ const Form = ({ }} mt={'4px'} textAlign={'center'}> - {item.label} + {item?.label} ) @@ -548,7 +548,7 @@ const Form = ({ }} mt={'4px'} textAlign={'center'}> - {item.label} + {item?.label} ) @@ -778,7 +778,7 @@ const Form = ({ alignItems={'center'} h={'32px'} bg={'grayModern.50'} - px={2} + px={4} border={theme.borders.base} borderLeft={0} borderTopRightRadius={'md'} @@ -787,9 +787,11 @@ const Form = ({ {network.customDomain ? network.customDomain : network.publicDomain} setCustomAccessModalData({ publicDomain: network.publicDomain, diff --git a/frontend/providers/devbox/app/[lang]/(platform)/devbox/create/page.tsx b/frontend/providers/devbox/app/[lang]/(platform)/devbox/create/page.tsx index 046b10011da..d6974c30d62 100644 --- a/frontend/providers/devbox/app/[lang]/(platform)/devbox/create/page.tsx +++ b/frontend/providers/devbox/app/[lang]/(platform)/devbox/create/page.tsx @@ -99,12 +99,12 @@ const DevboxCreatePage = () => { ? languageVersionMap[runtime as LanguageTypeEnum]?.[0]?.id || frameworkVersionMap[runtime as FrameworkTypeEnum]?.[0]?.id || osVersionMap[runtime as OSTypeEnum]?.[0]?.id - : languageVersionMap[LanguageTypeEnum.go][0].id, + : '', networks: ( languageVersionMap[runtime as LanguageTypeEnum]?.[0]?.defaultPorts || frameworkVersionMap[runtime as FrameworkTypeEnum]?.[0]?.defaultPorts || osVersionMap[runtime as OSTypeEnum]?.[0]?.defaultPorts || - languageVersionMap[LanguageTypeEnum.go][0].defaultPorts + [] ).map((port) => ({ networkName: `${defaultDevboxEditValue.name}-${nanoid()}`, portName: nanoid(), diff --git a/frontend/providers/devbox/app/[lang]/(platform)/devbox/detail/[name]/components/Header.tsx b/frontend/providers/devbox/app/[lang]/(platform)/devbox/detail/[name]/components/Header.tsx index 55b9b4e56fe..c5541257358 100644 --- a/frontend/providers/devbox/app/[lang]/(platform)/devbox/detail/[name]/components/Header.tsx +++ b/frontend/providers/devbox/app/[lang]/(platform)/devbox/detail/[name]/components/Header.tsx @@ -14,6 +14,7 @@ import MyIcon from '@/components/Icon' import IDEButton from '@/components/IDEButton' import DelModal from '@/components/modals/DelModal' import DevboxStatusTag from '@/components/DevboxStatusTag' +import { sealosApp } from 'sealos-desktop-sdk/app' const Header = ({ refetchDevboxDetail, @@ -98,6 +99,27 @@ const Header = ({ }, [setLoading, t, toast, refetchDevboxDetail] ) + const handleGoToTerminal = useCallback( + async (devbox: DevboxDetailType) => { + const defaultCommand = `kubectl exec -it $(kubectl get po -l app.kubernetes.io/name=${devbox.name} -oname) -- sh -c "clear; (bash || ash || sh)"` + try { + sealosApp.runEvents('openDesktopApp', { + appKey: 'system-terminal', + query: { + defaultCommand + }, + messageData: { type: 'new terminal', command: defaultCommand } + }) + } catch (error: any) { + toast({ + title: typeof error === 'string' ? error : error.message || t('jump_terminal_error'), + status: 'error' + }) + console.error(error) + } + }, + [t, toast] + ) return ( {/* left back button and title */} @@ -161,6 +183,19 @@ const Header = ({ }} /> + {devboxDetail.status.value === 'Running' && ( @@ -159,16 +137,12 @@ const IDEButton = ({ fontSize={'12px'} defaultValue={currentIDE} px={1}> - {[ - { value: 'vscode' as IDEType, label: 'VSCode' }, - { value: 'cursor' as IDEType, label: 'Cursor' }, - { value: 'vscodeInsider' as IDEType, label: 'VSCode Insider' } - ].map((item) => ( + {menuItems.map((item) => ( setCurrentIDE(item.value)} - icon={} + onClick={() => setCurrentIDE(item.value as IDEType)} + icon={} _hover={{ bg: '#1118240D', borderRadius: 4 @@ -178,7 +152,7 @@ const IDEButton = ({ borderRadius: 4 }}> - {item.label} + {item?.label} {currentIDE === item.value && } @@ -189,34 +163,33 @@ const IDEButton = ({ ) } -const getCurrentIDELabelAndIcon = ( - currentIDE: IDEType -): { - label: string - icon: IDEType -} => { - switch (currentIDE) { - case 'vscode': - return { - label: 'VSCode', - icon: 'vscode' - } - case 'cursor': - return { - label: 'Cursor', - icon: 'cursor' - } - case 'vscodeInsider': - return { - label: 'VSCode Insider', - icon: 'vscodeInsider' - } - default: - return { - label: 'VSCode', - icon: 'vscode' - } +export const ideObj = { + vscode: { + label: 'VSCode', + icon: 'vscode', + prefix: 'vscode://', + value: 'vscode' + }, + vscodeInsiders: { + label: 'VSCode Insiders', + icon: 'vscodeInsiders', + prefix: 'vscode-insiders://', + value: 'vscodeInsiders' + }, + cursor: { + label: 'Cursor', + icon: 'cursor', + prefix: 'cursor://', + value: 'cursor' + }, + windsurf: { + label: 'Windsurf', + icon: 'windsurf', + prefix: 'windsurf://', + value: 'windsurf' } } +const menuItems = Object.values(ideObj).map(({ value, label }) => ({ value, label })) + export default IDEButton diff --git a/frontend/providers/devbox/components/Icon/icons/vscodeInsider.svg b/frontend/providers/devbox/components/Icon/icons/vscodeInsiders.svg similarity index 100% rename from frontend/providers/devbox/components/Icon/icons/vscodeInsider.svg rename to frontend/providers/devbox/components/Icon/icons/vscodeInsiders.svg diff --git a/frontend/providers/devbox/components/Icon/icons/windsurf.svg b/frontend/providers/devbox/components/Icon/icons/windsurf.svg new file mode 100644 index 00000000000..c88cf74a394 --- /dev/null +++ b/frontend/providers/devbox/components/Icon/icons/windsurf.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/frontend/providers/devbox/components/Icon/index.tsx b/frontend/providers/devbox/components/Icon/index.tsx index 46a78e85080..9096d965663 100644 --- a/frontend/providers/devbox/components/Icon/index.tsx +++ b/frontend/providers/devbox/components/Icon/index.tsx @@ -58,11 +58,12 @@ const map = { list: require('./icons/list.svg').default, maximize: require('./icons/maximize.svg').default, chevronDown: require('./icons/chevronDown.svg').default, - vscodeInsider: require('./icons/vscodeInsider.svg').default, + vscodeInsiders: require('./icons/vscodeInsiders.svg').default, cursor: require('./icons/cursor.svg').default, check: require('./icons/check.svg').default, empty: require('./icons/empty.svg').default, - shutdown: require('./icons/shutdown.svg').default + shutdown: require('./icons/shutdown.svg').default, + windsurf: require('./icons/windsurf.svg').default } const MyIcon = ({ diff --git a/frontend/providers/devbox/components/PriceBox.tsx b/frontend/providers/devbox/components/PriceBox.tsx index 5851c6b2372..81562d775e7 100644 --- a/frontend/providers/devbox/components/PriceBox.tsx +++ b/frontend/providers/devbox/components/PriceBox.tsx @@ -68,9 +68,9 @@ const PriceBox = ({ {priceList.map((item) => ( - + - {t(item.label)}: + {t(item?.label)}: {item.value} diff --git a/frontend/providers/devbox/components/modals/releaseModal.tsx b/frontend/providers/devbox/components/modals/releaseModal.tsx index 79154448ede..56ef6443b2a 100644 --- a/frontend/providers/devbox/components/modals/releaseModal.tsx +++ b/frontend/providers/devbox/components/modals/releaseModal.tsx @@ -19,7 +19,7 @@ import { useCallback, useState } from 'react' import { useEnvStore } from '@/stores/env' import { useConfirm } from '@/hooks/useConfirm' import { DevboxListItemType } from '@/types/devbox' -import { pauseDevbox, releaseDevbox, restartDevbox } from '@/api/devbox' +import { pauseDevbox, releaseDevbox, startDevbox } from '@/api/devbox' const ReleaseModal = ({ onClose, @@ -64,18 +64,22 @@ const ReleaseModal = ({ async (enableRestartMachine: boolean) => { try { setLoading(true) + // 1.pause devbox if (devbox.status.value === 'Running') { await pauseDevbox({ devboxName: devbox.name }) + // wait 3s + await new Promise((resolve) => setTimeout(resolve, 3000)) } + // 2.release devbox await releaseDevbox({ devboxName: devbox.name, tag, releaseDes, devboxUid: devbox.id }) - + // 3.start devbox if (enableRestartMachine) { - await restartDevbox({ devboxName: devbox.name }) + await startDevbox({ devboxName: devbox.name }) } toast({ title: t('submit_release_successful'), diff --git a/frontend/providers/devbox/constants/devbox.ts b/frontend/providers/devbox/constants/devbox.ts index bd8917eee49..c1e825d878c 100644 --- a/frontend/providers/devbox/constants/devbox.ts +++ b/frontend/providers/devbox/constants/devbox.ts @@ -3,6 +3,7 @@ import { DevboxEditType, DevboxDetailType } from '@/types/devbox' export const crLabelKey = 'sealos-devbox-cr' export const devboxKey = 'cloud.sealos.io/devbox-manager' export const publicDomainKey = `cloud.sealos.io/app-deploy-manager-domain` +export const ingressProtocolKey = 'nginx.ingress.kubernetes.io/backend-protocol' export enum LanguageTypeEnum { java = 'java', diff --git a/frontend/providers/devbox/message/en.json b/frontend/providers/devbox/message/en.json index b3c814eb1a3..9c9f12d2bb3 100644 --- a/frontend/providers/devbox/message/en.json +++ b/frontend/providers/devbox/message/en.json @@ -22,7 +22,6 @@ "This runtime field is required": "The runtime version field is required", "basic_configuration": "Basic", "basic_info": "Basic", - "start": "Start", "cancel": "Cancel", "code_server": "CodeServer", "config_form": "Form", @@ -47,6 +46,7 @@ "delete": "Delete", "delete_failed": "Delete failed", "delete_successful": "Delete succeeded", + "delete_version_confirm_info": "Are you sure you want to delete this version?", "delete_warning": "Deletion Warning", "delete_warning_content": "Are you sure you want to delete Devbox?", "delete_warning_content_2": "Deleting Devbox will cause the remote environment to be deleted and you will not be able to access the remote development environment. (Your released version will be remaining).", @@ -71,7 +71,7 @@ "event": "Event", "export_privateKey": "Export privateKey", "export_yaml": "Export YAML", - "external_address": "External Address", + "external_address": "Public Address", "framework": "Framework", "ide_tooltip": "Click to develop in IDE", "image": "Image", @@ -94,6 +94,7 @@ "not_allow_standalone_use": "Not allowed to use standalone", "open_link": "Open link", "open_vscode": "VS Code", + "opening_ide": "Opening IDE...", "os": "OS", "pause": "Shutdown", "pause_devbox_info": "Automatically start the machine after released.", @@ -108,7 +109,10 @@ "recent_error": "Recent Errors", "release": "Release", "release_confirm_info": "During the release process, the machine will be temporarily shut down and the release will be in the current state. Please save the running project.", + "release_failed": "Release failed", + "release_pending": "Releasing...", "release_prompt": "The release process will Shutdown the machine and release in the current state.", + "release_success": "Release success", "release_successful": "Release succeeded", "release_version": "Release", "remaining": "Available", @@ -122,6 +126,7 @@ "shutdown": "Shutdown", "ssh_config": "SSH Configuration", "ssh_connect_info": "SSH Connection String", + "start": "Start", "start_error": "Start error", "start_runtime": "Runtime", "start_success": "Start succeeded", @@ -148,12 +153,7 @@ "version_info": "Version List", "version_list": "Version List", "version_number": "Tag", - "release_success": "Release success", - "release_pending": "Releasing...", - "release_failed": "Release failed", "vscode": "VS Code", "vscode_tooltip": "Click to develop in VSCode", - "yaml_file": "YAML", - "delete_version_confirm_info": "Are you sure you want to delete this version?", - "opening_ide": "Opening IDE..." + "yaml_file": "YAML" } diff --git a/frontend/providers/devbox/message/zh.json b/frontend/providers/devbox/message/zh.json index 8718f816394..7a71635534b 100644 --- a/frontend/providers/devbox/message/zh.json +++ b/frontend/providers/devbox/message/zh.json @@ -22,7 +22,6 @@ "This runtime field is required": "运行时版本是必填项", "basic_configuration": "基础配置", "basic_info": "基础信息", - "start": "开机", "cancel": "取消", "code_server": "CodeServer", "config_form": "配置表单", @@ -48,6 +47,7 @@ "delete": "删除", "delete_failed": "删除失败", "delete_successful": "删除成功", + "delete_version_confirm_info": "你确定要删除该版本吗?", "delete_warning": "删除警告", "delete_warning_content": "您确定要删除该云沙箱嘛?", "delete_warning_content_2": "删除云沙箱会导致云沙箱远程环境被删除,您将无法访问远程开发环境。(您已发布的版本仍然会保留)。", @@ -72,7 +72,7 @@ "event": "事件", "export_privateKey": "导出私钥", "export_yaml": "导出 YAML", - "external_address": "外网地址", + "external_address": "公网地址", "framework": "框架", "ide_tooltip": "点击在 IDE 中开发", "image": "镜像", @@ -96,6 +96,7 @@ "not_allow_standalone_use": "不允许独立使用", "open_link": "打开链接", "open_vscode": "VS Code", + "opening_ide": "正在打开 IDE...", "os": "操作系统", "pause": "关机", "pause_devbox_info": "发布后自动启动机器", @@ -110,7 +111,10 @@ "recent_error": "最近错误", "release": "发布", "release_confirm_info": "发版过程中将暂时关闭机器,且以当前状态发版,请保存好正在运行的项目。", + "release_failed": "发版失败", + "release_pending": "发版中", "release_prompt": "发版过程将关机,以当前状态发版。", + "release_success": "发版成功", "release_successful": "发版成功", "release_version": "发布版本", "remaining": "剩余", @@ -124,6 +128,7 @@ "shutdown": "关机", "ssh_config": "SSH 配置", "ssh_connect_info": "连接串", + "start": "开机", "start_error": "开机失败", "start_runtime": "启动环境", "start_success": "开机成功", @@ -150,13 +155,8 @@ "version_info": "版本列表", "version_list": "版本列表", "version_number": "版本号", - "release_success": "发版成功", - "release_pending": "发版中", - "release_failed": "发版失败", "vscode": "VS Code", "vscodeInsider": "VSCode Insider", "vscode_tooltip": "点击在 VSCode 中开发", - "yaml_file": "YAML 文件", - "delete_version_confirm_info": "你确定要删除该版本吗?", - "opening_ide": "正在打开 IDE..." + "yaml_file": "YAML 文件" } diff --git a/frontend/providers/devbox/stores/global.ts b/frontend/providers/devbox/stores/global.ts index 0f52630641b..c40b4f8655d 100644 --- a/frontend/providers/devbox/stores/global.ts +++ b/frontend/providers/devbox/stores/global.ts @@ -2,7 +2,7 @@ import { create } from 'zustand' import { devtools } from 'zustand/middleware' import { immer } from 'zustand/middleware/immer' -export type IDEType = 'vscode' | 'cursor' | 'vscodeInsider' +export type IDEType = 'vscode' | 'cursor' | 'vscodeInsiders' | 'windsurf' type State = { screenWidth: number diff --git a/frontend/providers/devbox/types/k8s.d.ts b/frontend/providers/devbox/types/k8s.d.ts index 69767db4993..481131ee99d 100644 --- a/frontend/providers/devbox/types/k8s.d.ts +++ b/frontend/providers/devbox/types/k8s.d.ts @@ -59,6 +59,7 @@ export interface KBDevboxSpec { network: { type: 'NodePort' | 'Tailnet' extraPorts: { + // NOTE: this object is deprecated, will be removed in the future containerPort: number hostPort?: number protocol?: string