diff --git a/images/icon.png b/images/icon.png index 74539272..04a74628 100755 Binary files a/images/icon.png and b/images/icon.png differ diff --git a/images/icon/namespace_sleep.svg b/images/icon/namespace_sleep.svg new file mode 100644 index 00000000..8e58330c --- /dev/null +++ b/images/icon/namespace_sleep.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/images/icon/vcluster_active.svg b/images/icon/vcluster_active.svg new file mode 100644 index 00000000..aaf84295 --- /dev/null +++ b/images/icon/vcluster_active.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/images/icon/vcluster_warning.svg b/images/icon/vcluster_warning.svg new file mode 100644 index 00000000..b16a1dff --- /dev/null +++ b/images/icon/vcluster_warning.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 8584a188..2cbfdc68 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5235,9 +5235,9 @@ } }, "follow-redirects": { - "version": "1.14.1", - "resolved": "https://enzuo-npm.pkg.coding.net/blog/public/__tarballs/follow-redirects/1.14.1/__path/npm/follow-redirects/-/follow-redirects-1.14.1/d9114ded0a1cfdd334e164e6662ad02bfd91ff43.tgz", - "integrity": "sha512-HWqDgT7ZEkqRzBvc2s64vSZ/hfOceEol3ac/7tKwzuvEyWx3/4UegXh5oBOIotkGsObyk3xznnSRVADBgWSQVg==" + "version": "1.14.7", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.7.tgz", + "integrity": "sha512-+hbxoLbFMbRKDwohX8GkTataGqO6Jb7jGwpAlwgy2bIz25XtRm7KEzJM76R1WiNT5SwZkX4Y75SwBolkpmE7iQ==" }, "for-in": { "version": "1.0.2", diff --git a/package.json b/package.json index 3349fa24..c2a217ab 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "nocalhost", - "version": "0.6.9", + "version": "v0.6.13", "displayName": "Nocalhost", "description": "Makes developing with Kubernetes feel like on local. IDE tool for cloud-native development", "license": "Apache-2.0", @@ -36,7 +36,7 @@ "main": "./dist/extension.js", "nhctl": { "serverVersion": "0.4.7", - "version": "0.6.1" + "version": "0.6.13" }, "contributes": { "configuration": [ @@ -230,9 +230,19 @@ "when": "viewItem =~ /^workload-(deployment|statefulSet|job|daemonSet|cronjob|pod|crd-resources)-dev-(?!vpn_)/i", "group": "2" }, + { + "command": "Nocalhost.wakeUp", + "when": "viewItem =~ /^devspace-server-sleeping(?!-vcluster)/i", + "group": "navigation" + }, + { + "command": "Nocalhost.forceSleep", + "when": "viewItem =~ /^devspace-server-unsleeping(?!-vcluster)/i", + "group": "navigation" + }, { "command": "Nocalhost.resetDevspace", - "when": "viewItem =~ /^devspace-server/i", + "when": "viewItem =~ /^devspace-server(? { @@ -71,13 +105,13 @@ export default class AccountClusterService { // refresh token if (config.url === "/v1/token/refresh") { host.log( - `Please login again ${this.loginInfo.baseUrl || ""}:${ + `Please login again ${this.loginInfo.baseUrl || ""}:${ this.loginInfo.username || "" }`, true ); host.showWarnMessage( - `Please login again ${this.loginInfo.baseUrl || ""}:${ + `Please login again ${this.loginInfo.baseUrl || ""}:${ this.loginInfo.username || "" }` ); @@ -107,19 +141,12 @@ export default class AccountClusterService { } static getServerClusterRootNodes = async ( - newAccountCluser: AccountClusterNode + accountCluster: AccountClusterNode ): Promise => { - if (!newAccountCluser) { + if (!accountCluster) { return []; } - const accountClusterService = new AccountClusterService( - newAccountCluser.loginInfo - ); - accountClusterService.accountClusterNode = newAccountCluser; - accountClusterService.jwt = newAccountCluser.jwt; - accountClusterService.refreshToken = newAccountCluser.refreshToken; - - const newRootNodes: IRootNode[] = []; + const accountClusterService = new AccountClusterService(accountCluster); let serviceAccounts = await accountClusterService.getServiceAccount(); logger.info( @@ -128,12 +155,10 @@ export default class AccountClusterService { }` ); - if (!Array.isArray(serviceAccounts) || serviceAccounts.length === 0) { - const msg = `no cluster found for ${newAccountCluser.loginInfo.baseUrl} ${newAccountCluser.loginInfo.username}`; - logger.error(msg); - - throw Error("No clusters"); - } + assert( + Array.isArray(serviceAccounts) && serviceAccounts.length > 0, + "No clusters" + ); const applications: IV2ApplicationInfo[] = await accountClusterService.getV2Application(); logger.info( @@ -144,116 +169,132 @@ export default class AccountClusterService { const kubeConfigArr: Array = []; - for (const sa of serviceAccounts) { - let devSpaces: Array | undefined = new Array(); + const serviceNodes = serviceAccounts.map(async (serviceAccount) => { + const kubeconfig = await AccountClusterService.startVClusterProcess( + serviceAccount + ); + + if (kubeconfig) { + serviceAccount.kubeconfig = kubeconfig; + } const { id, kubeConfigPath } = await AccountClusterService.saveKubeConfig( - sa + serviceAccount ); kubeConfigArr.push(id); - if (sa.privilege) { - const devs = await getAllNamespace({ - kubeConfigPath: kubeConfigPath, - namespace: "default", - }); - - for (const dev of devs) { - dev.storageClass = sa.storageClass; - dev.devStartAppendCommand = [ - "--priority-class", - "nocalhost-container-critical", - ]; - dev.kubeconfig = sa.kubeconfig; - - const ns = sa.namespacePacks?.find( - (ns) => ns.namespace === dev.namespace - ); - - dev.spaceId = ns?.spaceId; - dev.spaceName = ns?.spacename; - - if (sa.privilegeType === "CLUSTER_ADMIN") { - dev.spaceOwnType = "Owner"; - } else if (sa.privilegeType === "CLUSTER_VIEWER") { - dev.spaceOwnType = ns?.spaceOwnType ?? "Viewer"; - } - } - devSpaces.push(...devs); - } else { - for (const ns of sa.namespacePacks) { - const devInfo: IDevSpaceInfo = { - id: ns.spaceId, - spaceName: ns.spacename, - namespace: ns.namespace, - kubeconfig: sa.kubeconfig, - accountClusterService, - clusterId: sa.clusterId, - storageClass: sa.storageClass, - spaceOwnType: ns.spaceOwnType, - devStartAppendCommand: [ - "--priority-class", - "nocalhost-container-critical", - ], - }; - devSpaces.push(devInfo); - } - } + const state = await checkCluster(kubeConfigPath); - const obj: IRootNode = { - devSpaces, + return { + serviceAccount, + devSpaces: [], applications, - userInfo: newAccountCluser.userInfo, clusterSource: ClusterSource.server, - accountClusterService, - id: newAccountCluser.id, - createTime: newAccountCluser.createTime, + id: accountCluster.id, + createTime: accountCluster.createTime, kubeConfigPath, - state: await checkCluster(kubeConfigPath), - }; + state, + clusterInfo: accountCluster, + } as IRootNode; + }); - newRootNodes.push(obj); - } + const rootNodes = await (await Promise.allSettled(serviceNodes)).map( + (item) => { + if (item.status === "fulfilled") { + return item.value; + } + return { + clusterSource: ClusterSource.server, + id: accountCluster.id, + createTime: accountCluster.createTime, + kubeConfigPath: null, + } as IRootNode; + } + ); await AccountClusterService.cleanDiffKubeConfig( - newAccountCluser, + accountCluster, kubeConfigArr ); - return newRootNodes; + return rootNodes; }; + static async startVClusterProcess(sa: IServiceAccountInfo) { + const { virtualCluster, kubeconfigType } = sa; + if ( + kubeconfigType === "vcluster" && + virtualCluster.serviceType === "ClusterIP" + ) { + const { + serviceNamespace, + servicePort, + hostClusterContext, + serviceAddress, + } = virtualCluster; + + let oldInfo = virtualClusterProcMap[serviceAddress]; + if (oldInfo?.proc.killed === false) { + return oldInfo.kubeconfig; + } + + const { proc, kubeconfig } = await kubeConfigRender({ + namespace: serviceNamespace, + kubeconfig: sa.kubeconfig, + remotePort: servicePort, + context: hostClusterContext, + serviceAddress: serviceAddress, + }); + + const kubeConfigPath = path.resolve( + KUBE_CONFIG_DIR, + getStringHash(kubeconfig.trim()) + ); + + host.getContext().subscriptions.push({ + dispose: () => { + loggerDebug.debug("dispose", kubeConfigPath); + + proc.kill(); + + kubeconfigCommand(kubeConfigPath, "remove"); + + return fs.unlink(kubeConfigPath); + }, + }); + virtualClusterProcMap[serviceAddress] = { + proc, + kubeconfig, + }; + + return kubeconfig; + } + } static async cleanDiffKubeConfig( - accountCluser: AccountClusterNode, + accountCluster: AccountClusterNode, configs: Array ) { - const { baseUrl, username } = accountCluser.loginInfo; + const { baseUrl, username } = accountCluster.loginInfo; const KEY = `USER_LINK:${baseUrl}@${username}`; - const prevData = host.getGlobalState(KEY); + const prevData = host.getGlobalState>(KEY); + + host.setGlobalState(KEY, configs); if (prevData) { - const diff = difference(prevData as Array, configs); + const diff = difference(prevData, configs); if (diff.length === 0) { return; } - await Promise.allSettled( - diff.map((id) => { - return new Promise(async (res) => { - const file = path.resolve(KUBE_CONFIG_DIR, id); + diff.map((id) => { + const file = path.resolve(KUBE_CONFIG_DIR, id); - await kubeconfig(file, "remove"); - - res(); - }); - }) - ); + kubeconfigCommand(file, "remove"); + }); } - - host.setGlobalState(KEY, configs); } static async saveKubeConfig(accountInfo: IServiceAccountInfo) { @@ -264,42 +305,52 @@ export default class AccountClusterService { if (!(await isExist(kubeConfigPath))) { await writeFileLock(kubeConfigPath, accountInfo.kubeconfig); - kubeconfig(kubeConfigPath, "add"); + kubeconfigCommand(kubeConfigPath, "add"); } return { id, kubeConfigPath }; } + static appendClusterByLoginInfo = async ( loginInfo: LoginInfo ): Promise => { - const accountServer = new AccountClusterService(loginInfo); - const newAccountCluser = await accountServer.buildAccountClusterNode(); + const accountServer = new AccountClusterService({ + loginInfo, + userInfo: null, + jwt: null, + id: null, + createTime: Date.now(), + refreshToken: null, + state: { code: 200 }, + }); + + const newAccountCluster = await accountServer.buildAccountClusterNode(); + + let globalAccountClusterList = host.getGlobalState< + Array + >(SERVER_CLUSTER_LIST); - let globalAccountClusterList = host.getGlobalState(SERVER_CLUSTER_LIST); if (!Array.isArray(globalAccountClusterList)) { globalAccountClusterList = []; } - globalAccountClusterList = globalAccountClusterList.filter( - (it: AccountClusterNode) => it.id - ); + globalAccountClusterList = globalAccountClusterList.filter((it) => it.id); const oldAccountIndex = globalAccountClusterList.findIndex( - (it: AccountClusterNode) => it.id === newAccountCluser.id + (it) => it.id === newAccountCluster.id ); if (oldAccountIndex !== -1) { - globalAccountClusterList.splice(oldAccountIndex, 1, newAccountCluser); + globalAccountClusterList.splice(oldAccountIndex, 1, newAccountCluster); } else { - globalAccountClusterList.push(newAccountCluser); + globalAccountClusterList.push(newAccountCluster); } globalAccountClusterList = uniqBy(globalAccountClusterList, "id"); host.setGlobalState(SERVER_CLUSTER_LIST, globalAccountClusterList); - return newAccountCluser; + return newAccountCluster; }; - buildAccountClusterNode = async (): Promise => { await this.login(this.loginInfo); const userInfo = await this.getUserInfo(); @@ -362,11 +413,11 @@ export default class AccountClusterService { } deleteAccountNode() { - if (this.accountClusterNode) { + if (this.accountCluster) { let globalClusterRootNodes: AccountClusterNode[] = host.getGlobalState(SERVER_CLUSTER_LIST) || []; const index = globalClusterRootNodes.findIndex( - ({ id }) => id === this.accountClusterNode.id + ({ id }) => id === this.accountCluster.id ); if (index !== -1) { globalClusterRootNodes.splice(index, 1); @@ -375,10 +426,9 @@ export default class AccountClusterService { } } - // update login infoo updateLoginInfo() { - const newAccountCluser = { - ...this.accountClusterNode, + const newAccountCluster = { + ...this.accountCluster, jwt: this.jwt, refreshToken: this.refreshToken, }; @@ -390,14 +440,15 @@ export default class AccountClusterService { (it: AccountClusterNode) => it.id ); const oldAccountIndex = globalAccountClusterList.findIndex( - (it: AccountClusterNode) => it.id === newAccountCluser.id + (it: AccountClusterNode) => it.id === newAccountCluster.id ); if (oldAccountIndex !== -1) { - globalAccountClusterList.splice(oldAccountIndex, 1, newAccountCluser); + globalAccountClusterList.splice(oldAccountIndex, 1, newAccountCluster); } else { - globalAccountClusterList.push(newAccountCluser); + globalAccountClusterList.push(newAccountCluster); } globalAccountClusterList = uniqBy(globalAccountClusterList, "id"); + host.setGlobalState(SERVER_CLUSTER_LIST, globalAccountClusterList); } @@ -415,14 +466,14 @@ export default class AccountClusterService { } catch (e) { logger.error("getServiceAccount", e); - throw new Error( + throw Error( `failed to get cluster for ${this.loginInfo.baseUrl}@${this.loginInfo.username}` ); } } async getApplication() { - const { userInfo } = this.accountClusterNode; + const { userInfo } = this.accountCluster; try { const response = await this.instance.get( `/v1/users/${userInfo.id}/applications` @@ -446,7 +497,7 @@ export default class AccountClusterService { async checkServerVersion(): Promise { const res = await this.getVersion(); - const log = `checkVersion serverVersion:${res.data?.version} packageVerison:${packageJson.nhctl.serverVersion}`; + const log = `checkVersion serverVersion:${res.data?.version} packageVersion:${packageJson.nhctl.serverVersion}`; logger.info(log); if (res.data?.version) { @@ -460,7 +511,7 @@ export default class AccountClusterService { } async getV2Application(): Promise { - const { userInfo } = this.accountClusterNode; + const { userInfo } = this.accountCluster; const userId = userInfo.id; if (!userId) { return []; @@ -512,6 +563,21 @@ export default class AccountClusterService { const { data } = response.data; return data; } - throw new Error("Fail to fetch user infomation."); + throw Error("Fail to fetch user information."); + } + + async wakeUpSpace(id: number) { + const response = await this.instance.post(`/v2/dev_space/${id}/wakeup`); + const flag = response.data.code === 0; + if (response.status === 200 && response.data.code === 0) { + host.showInformationMessage("wakeUp success"); + } + } + + async sleepSpace(id: number) { + const response = await this.instance.post(`/v2/dev_space/${id}/sleep`); + if (response.status === 200 && response.data.code === 0) { + host.showInformationMessage("sleep success"); + } } } diff --git a/src/main/clusters/LocalCuster.ts b/src/main/clusters/LocalCuster.ts index 9556d778..e8266bd3 100644 --- a/src/main/clusters/LocalCuster.ts +++ b/src/main/clusters/LocalCuster.ts @@ -5,10 +5,9 @@ import * as fs from "fs"; import { LOCAL_PATH, KUBE_CONFIG_DIR } from "../constants"; import { isExistSync, writeFileAsync } from "../utils/fileUtil"; import { IRootNode } from "../domain"; -import { IDevSpaceInfo, IV2ApplicationInfo } from "../domain"; +import { IV2ApplicationInfo } from "../domain"; import { getStringHash } from "../utils/common"; -import * as yaml from "yaml"; -import { checkCluster, getAllNamespace, kubeconfig } from "../ctl/nhctl"; +import { checkCluster, kubeconfigCommand } from "../ctl/nhctl"; import { ClusterSource } from "../common/define"; import { ClustersState } from "."; @@ -21,12 +20,29 @@ export class LocalClusterNode { public clusterNickName?: string ) {} } +export function buildRootNodeForLocalCluster( + localCluster: LocalClusterNode, + state: ClustersState +): IRootNode { + const { filePath, createTime } = localCluster; + + return { + id: localCluster.id, + clusterName: localCluster.clusterNickName, + createTime, + clusterSource: ClusterSource.local, + applications: [], + kubeConfigPath: filePath, + state, + }; +} export default class LocalCluster { static getClusterNodeByKubeConfigPath( kubeConfigPath: string ): LocalClusterNode { - const localClusterNodes = host.getGlobalState(LOCAL_PATH) || []; + const localClusterNodes = + host.getGlobalState>(LOCAL_PATH) || []; return (localClusterNodes || []).find( (it: LocalClusterNode) => it.filePath === kubeConfigPath ); @@ -39,37 +55,9 @@ export default class LocalCluster { return; } const { filePath, createTime } = newLocalCluster; - let kubeConfig = ""; let applications: IV2ApplicationInfo[] = []; - let devSpaces: Array | undefined = new Array(); - if (!isExistSync(filePath)) { - host.log(`no such file or directory: ${filePath}`); - return; - } - const kubeStr = fs.readFileSync(filePath); - const kubeConfigObj = yaml.parse(`${kubeStr}`); - kubeConfig = `${kubeStr}`; - const contexts = kubeConfigObj["contexts"]; - if (!contexts || contexts.length === 0) { - return; - } - let defaultNamespace = contexts[0]["context"]["namespace"] || ""; - if (kubeConfigObj["current-context"]) { - const currentContext = contexts.find( - (it: any) => it.name === kubeConfigObj["current-context"] - ); - if (currentContext) { - defaultNamespace = currentContext.context.namespace; - } - } - const state = await checkCluster(filePath); - if (state.code === 200) { - devSpaces = await getAllNamespace({ - kubeConfigPath: filePath, - namespace: defaultNamespace as string, - }); - } + const state = await checkCluster(filePath); const contextObj = { applicationName: "default.application", @@ -90,7 +78,6 @@ export default class LocalCluster { }); const obj: IRootNode = { id: newLocalCluster.id, - devSpaces, clusterName: newLocalCluster.clusterNickName, createTime, clusterSource: ClusterSource.local, @@ -147,7 +134,7 @@ export default class LocalCluster { localClusterNodes.push(newCluster); host.setGlobalState(LOCAL_PATH, localClusterNodes); - await kubeconfig(resultFilePath, "add"); + await kubeconfigCommand(resultFilePath, "add"); return newCluster; } else { diff --git a/src/main/clusters/utils.ts b/src/main/clusters/utils.ts index 0f21b87f..2c55502f 100644 --- a/src/main/clusters/utils.ts +++ b/src/main/clusters/utils.ts @@ -3,7 +3,7 @@ import host from "../host"; export function isExistCluster() { const globalClusterAccountList = - host.getGlobalState(SERVER_CLUSTER_LIST) || []; - const localPaths = (host.getGlobalState(LOCAL_PATH) as string[]) || []; + host.getGlobalState>(SERVER_CLUSTER_LIST) || []; + const localPaths = host.getGlobalState>(LOCAL_PATH) || []; return globalClusterAccountList.length > 0 || localPaths.length > 0; } diff --git a/src/main/commands/DeleteKubeConfigCommand.ts b/src/main/commands/DeleteKubeConfigCommand.ts index 4f9d6800..20d31d5d 100644 --- a/src/main/commands/DeleteKubeConfigCommand.ts +++ b/src/main/commands/DeleteKubeConfigCommand.ts @@ -10,7 +10,7 @@ import host from "../host"; import { LocalClusterNode } from "../clusters/LocalCuster"; import { KubeConfigNode } from "../nodes/KubeConfigNode"; import Bookinfo from "../common/bookinfo"; -import { kubeconfig } from "../ctl/nhctl"; +import { kubeconfigCommand } from "../ctl/nhctl"; import { NocalhostRootNode } from "../nodes/NocalhostRootNode"; import messageBus from "../utils/messageBus"; @@ -58,7 +58,7 @@ export default class DeleteKubeConfigCommand implements ICommand { deleted.forEach(async (f) => { if (await isExist(f.filePath)) { - await kubeconfig(f.filePath, "remove"); + await kubeconfigCommand(f.filePath, "remove"); fs.unlinkSync(f.filePath); } diff --git a/src/main/commands/ResetDevspaceCommand.ts b/src/main/commands/ResetDevspaceCommand.ts index 08313957..79778ff9 100644 --- a/src/main/commands/ResetDevspaceCommand.ts +++ b/src/main/commands/ResetDevspaceCommand.ts @@ -7,9 +7,9 @@ import state from "../state"; import host, { Host } from "../host"; import * as nhctl from "../ctl/nhctl"; import { DevSpaceNode } from "../nodes/DevSpaceNode"; -import { NocalhostRootNode } from "../nodes/NocalhostRootNode"; import Bookinfo from "../common/bookinfo"; import messageBus from "../utils/messageBus"; +import { KubeConfigNode } from "../nodes/KubeConfigNode"; export default class ResetDevspaceCommand implements ICommand { command: string = RESET_DEVSPACE; @@ -39,36 +39,38 @@ export default class ResetDevspaceCommand implements ICommand { await vscode.commands.executeCommand("Nocalhost.refresh", node); host.disposeDevspace(node.info.spaceName); - await this.reset( - host, - node.getKubeConfigPath(), - node.info.namespace, - node.info.spaceName - ).finally(async () => { - await node.parent.accountClusterService.resetDevSpace(node.info.id); - const nocalhostRootNode = node.parent.parent as NocalhostRootNode; + await this.reset(host, node) + .then(() => { + messageBus.emit("refreshTree", {}); + }) + .finally(async () => { + const parent = node.parent.parent; - await nocalhostRootNode.updateData(); + await parent.updateData(); - vscode.commands.executeCommand("Nocalhost.refresh", nocalhostRootNode); + vscode.commands.executeCommand("Nocalhost.refresh", parent); - state.delete(node.info.spaceName); - }); - - messageBus.emit("refreshTree", {}); + state.delete(node.info.spaceName); + }); } - private async reset( - host: Host, - kubeconfigPath: string, - namespace: string, - devspaceName: string - ) { - host.log(`Reseting devspace: ${devspaceName}`, true); - await nhctl.resetApp(kubeconfigPath, namespace, devspaceName); - host.removeGlobalState(devspaceName); - host.log(`Devspace ${devspaceName} reset`, true); - host.showInformationMessage(`Devspace ${devspaceName} reset`); + private async reset(host: Host, node: DevSpaceNode) { + const { + getKubeConfigPath, + info: { namespace, spaceName }, + } = node; + + host.log(`Resetting devspace: ${spaceName}`, true); + + await nhctl.resetApp(getKubeConfigPath.call(node), namespace, spaceName); + + host.removeGlobalState(spaceName); + host.log(`Devspace ${spaceName} reset`, true); + host.showInformationMessage(`Devspace ${spaceName} reset`); + + await (node.parent as KubeConfigNode).accountClusterService?.resetDevSpace( + node.info.id + ); } } diff --git a/src/main/commands/ShowApplicationsCommand.ts b/src/main/commands/ShowApplicationsCommand.ts index 0a30da71..d48bac22 100644 --- a/src/main/commands/ShowApplicationsCommand.ts +++ b/src/main/commands/ShowApplicationsCommand.ts @@ -8,6 +8,7 @@ import { DevSpaceNode } from "../nodes/DevSpaceNode"; import AccountClusterService from "../clusters/AccountCluster"; import { ClusterSource } from "../common/define"; import { NhctlCommand } from "../ctl/nhctl"; +import { KubeConfigNode } from "../nodes/KubeConfigNode"; export default class ShowApplicationsCommand implements ICommand { command: string = SHOW_APP; @@ -30,10 +31,7 @@ export default class ShowApplicationsCommand implements ICommand { }).exec(); if (node.clusterSource === ClusterSource.server) { - const accountClusterService: AccountClusterService = - node.parent.accountClusterService; - - accountClusterService.checkServerVersion(); + await (node.parent as KubeConfigNode).accountClusterService?.checkServerVersion(); } const apps = node.getUninstallApps(); diff --git a/src/main/commands/SignInCommand.ts b/src/main/commands/SignInCommand.ts index 450593a3..e5d0337f 100644 --- a/src/main/commands/SignInCommand.ts +++ b/src/main/commands/SignInCommand.ts @@ -8,13 +8,7 @@ import { AccountCluster as AccountClusterService } from "../clusters"; import state from "../state"; import { NocalhostRootNode } from "../nodes/NocalhostRootNode"; import { NOCALHOST } from "../constants"; - -interface LoginInfo { - username: string; - from?: "plugin"; - password: string; - baseUrl: string; -} +import { LoginInfo } from "../clusters/interface"; export default class SignInCommand implements ICommand { command: string = SIGN_IN; diff --git a/src/main/commands/SignOutCommand.ts b/src/main/commands/SignOutCommand.ts index 8eee510e..b77370d7 100644 --- a/src/main/commands/SignOutCommand.ts +++ b/src/main/commands/SignOutCommand.ts @@ -1,5 +1,6 @@ import * as vscode from "vscode"; import * as path from "path"; +import { promises as fs } from "fs"; import ICommand from "./ICommand"; import { SIGN_OUT } from "./constants"; @@ -8,19 +9,25 @@ import registerCommand from "./register"; import state from "../state"; import { KUBE_CONFIG_DIR, NOCALHOST, SERVER_CLUSTER_LIST } from "../constants"; import host from "../host"; -import { IUserInfo } from "../domain"; +import { IRootNode, IUserInfo } from "../domain"; import { KubeConfigNode } from "../nodes/KubeConfigNode"; import Bookinfo from "../common/bookinfo"; -import { kubeconfig } from "../ctl/nhctl"; +import { kubeconfigCommand } from "../ctl/nhctl"; import { LoginInfo } from "../clusters/interface"; import { NocalhostRootNode } from "../nodes/NocalhostRootNode"; import messageBus from "../utils/messageBus"; +import { + AccountClusterNode, + virtualClusterProcMap, +} from "../clusters/AccountCluster"; +import { ClusterSource } from "../common/define"; export default class SignOutCommand implements ICommand { command: string = SIGN_OUT; constructor(context: vscode.ExtensionContext) { registerCommand(context, this.command, false, this.execCommand.bind(this)); } + loginInfo: LoginInfo; async execCommand(node: KubeConfigNode) { if (!node) { host.showWarnMessage("Failed to get node configs, please try again."); @@ -49,9 +56,12 @@ export default class SignOutCommand implements ICommand { Bookinfo.cleanCheck(node); const rootNode = state.getNode(NOCALHOST) as NocalhostRootNode; - await rootNode.deleteCluster(node.accountClusterService.loginInfo); - this.cleanKubeConfig(node.accountClusterService.loginInfo); + this.loginInfo = node.getClusterNode().loginInfo; + + this.dispose(); + + rootNode.deleteCluster(this.loginInfo); messageBus.emit("refreshTree", {}); } catch (error) { @@ -61,24 +71,59 @@ export default class SignOutCommand implements ICommand { } } - cleanKubeConfig(loginInfo: LoginInfo) { - const { baseUrl, username } = loginInfo; - const KEY = `USER_LINK:${baseUrl}@${username}`; + dispose() { + this.killVClusterProcess(); + this.cleanKubeConfig(); + } - const prevData = host.getGlobalState(KEY); + async killVClusterProcess() { + const rootNode = state.getData>("Nocalhost"); - if (prevData) { - Promise.allSettled( - (prevData as Array).map((id) => { - return new Promise(async (res, rej) => { - const file = path.resolve(KUBE_CONFIG_DIR, id); + const { username, baseUrl } = this.loginInfo; - await kubeconfig(file, "remove"); + rootNode + .filter((node) => node.clusterSource === ClusterSource.server) + .forEach((root) => { + const { loginInfo } = root.clusterInfo as AccountClusterNode; - res(); - }); - }) - ); + if (loginInfo.username !== username || loginInfo.baseUrl !== baseUrl) { + return; + } + + const { virtualCluster, kubeconfigType } = root.serviceAccount; + if ( + kubeconfigType === "vcluster" && + virtualCluster.serviceType === "ClusterIP" + ) { + const { serviceAddress } = virtualCluster; + + let info = virtualClusterProcMap[serviceAddress]; + + if (info?.proc.killed === false) { + info.proc.kill(); + + fs.unlink(root.kubeConfigPath); + + delete virtualClusterProcMap[serviceAddress]; + } + } + }); + } + + getFilePath(id: string) { + return path.resolve(KUBE_CONFIG_DIR, id); + } + + cleanKubeConfig() { + const { baseUrl, username } = this.loginInfo; + const KEY = `USER_LINK:${baseUrl}@${username}`; + + const prevData = host.getGlobalState>(KEY); + + if (prevData) { + prevData.map((id) => { + kubeconfigCommand(this.getFilePath(id), "remove"); + }); } host.removeGlobalState(KEY); diff --git a/src/main/commands/SleepingCommand/ForceSleep.ts b/src/main/commands/SleepingCommand/ForceSleep.ts new file mode 100644 index 00000000..cdfdac04 --- /dev/null +++ b/src/main/commands/SleepingCommand/ForceSleep.ts @@ -0,0 +1,36 @@ +import * as vscode from "vscode"; +import { FORCE_SLEEP } from "../constants"; +import ICommand from "../ICommand"; +import registerCommand from "../register"; +import host from "../../host"; +import AccountClusterService, { + AccountClusterNode, +} from "../../clusters/AccountCluster"; + +import { DevSpaceNode } from "../../nodes/DevSpaceNode"; + +export default class ForceSleep implements ICommand { + command: string = FORCE_SLEEP; + + constructor(context: vscode.ExtensionContext) { + registerCommand(context, this.command, false, this.execCommand.bind(this)); + } + + async execCommand(node: DevSpaceNode) { + // force sleep + const res = await host.showInformationMessage( + "Confirm to sleep!", + { modal: true }, + "yes" + ); + + if (res === "yes") { + const spaceId = node.info.spaceId; + const clusterInfo = node.parent?.rootNode?.clusterInfo; + const service = new AccountClusterService( + clusterInfo as AccountClusterNode + ); + service.sleepSpace(spaceId); + } + } +} diff --git a/src/main/commands/SleepingCommand/WakeUp.ts b/src/main/commands/SleepingCommand/WakeUp.ts new file mode 100644 index 00000000..1bff473a --- /dev/null +++ b/src/main/commands/SleepingCommand/WakeUp.ts @@ -0,0 +1,26 @@ +import * as vscode from "vscode"; +import { WAKE_UP } from "../constants"; +import ICommand from "../ICommand"; +import registerCommand from "../register"; + +import { DevSpaceNode } from "../../nodes/DevSpaceNode"; +import AccountClusterService, { + AccountClusterNode, +} from "../../clusters/AccountCluster"; + +export default class WakeUp implements ICommand { + command: string = WAKE_UP; + + constructor(context: vscode.ExtensionContext) { + registerCommand(context, this.command, false, this.execCommand.bind(this)); + } + + async execCommand(node: DevSpaceNode) { + const spaceId = node.info.spaceId; + const clusterInfo = node.parent?.rootNode?.clusterInfo; + const service = new AccountClusterService( + clusterInfo as AccountClusterNode + ); + service.wakeUpSpace(spaceId); + } +} diff --git a/src/main/commands/UpgradeCommand.ts b/src/main/commands/UpgradeCommand.ts index 37d73a77..9dce1c99 100644 --- a/src/main/commands/UpgradeCommand.ts +++ b/src/main/commands/UpgradeCommand.ts @@ -13,6 +13,7 @@ import { AppNode } from "../nodes/AppNode"; import AccountClusterService from "../clusters/AccountCluster"; import { DevSpaceNode } from "../nodes/DevSpaceNode"; import { ClusterSource } from "../common/define"; +import { KubeConfigNode } from "../nodes/KubeConfigNode"; export default class UpgradeCommand implements ICommand { command: string = UPGRADE_APP; @@ -28,10 +29,7 @@ export default class UpgradeCommand implements ICommand { const devSpaceNode = appNode.parent as DevSpaceNode; if (devSpaceNode.clusterSource === ClusterSource.server) { - const accountClusterService: AccountClusterService = - devSpaceNode.parent.accountClusterService; - - accountClusterService.checkServerVersion(); + await (devSpaceNode.parent as KubeConfigNode).accountClusterService?.checkServerVersion(); } let refOrVersion: string | undefined; diff --git a/src/main/commands/constants.ts b/src/main/commands/constants.ts index e02211e6..6026a364 100644 --- a/src/main/commands/constants.ts +++ b/src/main/commands/constants.ts @@ -65,3 +65,7 @@ export const ADD_KUBECONFIG = "Nocalhost.addKubeconfig"; export const CLUSTERS_VIEW = "Nocalhost.ClustersView"; export const CONFIG_URI_QUERY = "_configURIQuery"; + +// sleeping +export const FORCE_SLEEP = "Nocalhost.forceSleep"; +export const WAKE_UP = "Nocalhost.wakeUp"; diff --git a/src/main/commands/index.ts b/src/main/commands/index.ts index f473b802..386cd6d5 100644 --- a/src/main/commands/index.ts +++ b/src/main/commands/index.ts @@ -48,6 +48,9 @@ import StartProxyModeCommand from "./proxy/StartProxyModeCommand"; import ResumeProxyModeCommand from "./proxy/ResumeProxyModeCommand"; import EndProxyModeCommand from "./proxy/EndProxyModeCommand"; +import WakeUpCommand from "./SleepingCommand/WakeUp"; +import ForceSleepCommand from "./SleepingCommand/ForceSleep"; + export default function initCommands( context: vscode.ExtensionContext, appTreeProvider: NocalhostAppProvider @@ -109,4 +112,6 @@ export default function initCommands( new StartProxyModeCommand(context); new ResumeProxyModeCommand(context); new EndProxyModeCommand(context); + new ForceSleepCommand(context); + new WakeUpCommand(context); } diff --git a/src/main/ctl/nhctl/cli.ts b/src/main/ctl/nhctl/cli.ts index ac84709c..be5d06e8 100644 --- a/src/main/ctl/nhctl/cli.ts +++ b/src/main/ctl/nhctl/cli.ts @@ -10,6 +10,8 @@ import * as os from "os"; import * as path from "path"; import * as fs from "fs"; +import { ChildProcessWithoutNullStreams } from "child_process"; + import { exec, ExecParam, execWithProgress } from "../shell"; import host, { Host } from "../../host"; import * as yaml from "yaml"; @@ -19,7 +21,7 @@ import * as packageJson from "../../../../package.json"; import { NH_BIN } from "../../constants"; import services from "../../common/DataCenter/services"; import { SvcProfile, NodeInfo } from "../../nodes/types/nodeType"; -import logger from "../../utils/logger"; +import logger, { loggerDebug } from "../../utils/logger"; import { IDevSpaceInfo } from "../../domain"; import { Resource, ResourceStatus } from "../../nodes/types/resourceType"; import { downloadNhctl, lock, unlock } from "../../utils/download"; @@ -1472,7 +1474,7 @@ export async function checkCluster( return result; } -export async function kubeconfig( +export async function kubeconfigCommand( kubeConfigPath: string, command: "add" | "remove" ) { @@ -1482,7 +1484,7 @@ export async function kubeconfig( .toJson() .exec(); - logger.debug(`kubeconfig ${command}:${kubeConfigPath}`); + loggerDebug.debug(`kubeconfig ${command}:${kubeConfigPath}`); return result; } @@ -1554,6 +1556,68 @@ export async function associateQuery(param: { .toJson() .exec(); } +export async function kubeConfigRender(param: { + serviceAddress: string; + namespace: string; + kubeconfig: string; + context: string; + remotePort: string; +}) { + const { + namespace, + serviceAddress, + remotePort, + context, + kubeconfig: kubeconfigStr, + } = param; + + const commands = [ + NhctlCommand.nhctlPath, + "kubeconfig", + "render", + "--namespace", + namespace, + "--kubeconfig", + "-", + "--context", + context, + serviceAddress, + `:${remotePort}`, + ]; + + const END_Symbol = "EOF\n"; + + return new Promise<{ + kubeconfig: string; + proc: ChildProcessWithoutNullStreams; + }>((res, rej) => { + const command = commands.join(" "); + + const time = Date.now(); + + const { proc, promise } = exec({ + command: commands.join(" "), + output: false, + }); + + proc.stdout.on("data", (chuck: Buffer) => { + const str = chuck.toString(); + + if (str.endsWith(END_Symbol)) { + loggerDebug.debug(command, Date.now() - time); + + const kubeconfig = str.substring(0, str.lastIndexOf(END_Symbol)); + res({ kubeconfig, proc }); + return; + } + }); + + proc.stdin.write(kubeconfigStr); + proc.stdin.end(); + + promise.catch(rej); + }); +} export async function vpn(param: { subCommand: "connect" | "disconnect" | "reconnect"; diff --git a/src/main/ctl/shell.ts b/src/main/ctl/shell.ts index cc634207..c5ee6f99 100644 --- a/src/main/ctl/shell.ts +++ b/src/main/ctl/shell.ts @@ -155,7 +155,9 @@ export function createProcess(param: ExecParam) { command = `sudo -p "Password:" -S ${command}`; } - const proc = spawn(command, [], { shell: true, env }); + const commands = command.split(" "); + + const proc = spawn(commands.shift(), commands, { env }); const { err, out } = getOutput(output); let stderr = ""; @@ -287,5 +289,5 @@ export function getExecCommand(command: string) { if (host.isWindow() && process.env.ComSpec.endsWith("Git\\bin\\bash.exe")) { command = command.replaceAll(path.sep, "\\\\\\"); } - return command; + return command.trim(); } diff --git a/src/main/debug/nocalhost/start.ts b/src/main/debug/nocalhost/start.ts index 448c9349..7944e91b 100644 --- a/src/main/debug/nocalhost/start.ts +++ b/src/main/debug/nocalhost/start.ts @@ -107,9 +107,6 @@ async function getResourceNode() { if (node.clusterSource === ClusterSource.local) { name = await getClusterName({ clusterSource: ClusterSource.server, - devSpaces: node.devSpaceInfos, - applications: [], - state: { code: 200 }, kubeConfigPath: node.kubeConfigPath, }); } diff --git a/src/main/debug/provider/goDebugProvider.ts b/src/main/debug/provider/goDebugProvider.ts index f6f99c97..3b3b3cbf 100644 --- a/src/main/debug/provider/goDebugProvider.ts +++ b/src/main/debug/provider/goDebugProvider.ts @@ -7,7 +7,7 @@ import { commands, DebugConfiguration } from "vscode"; import { v4 } from "uuid"; import { delay } from "lodash"; -import logger from "../../utils/logger"; +import logger, { loggerDebug } from "../../utils/logger"; import { getPromiseWithAbort } from "../../utils"; import host from "../../host"; import { IDebugProvider } from "./IDebugProvider"; @@ -215,6 +215,6 @@ export class GoDebugProvider extends IDebugProvider { assert(result); - logger.debug("dlv GetVersion", result); + loggerDebug.debug("dlv GetVersion", result); } } diff --git a/src/main/debug/provider/java/javaDebugProvider.ts b/src/main/debug/provider/java/javaDebugProvider.ts index b7988cd6..1d363297 100644 --- a/src/main/debug/provider/java/javaDebugProvider.ts +++ b/src/main/debug/provider/java/javaDebugProvider.ts @@ -3,7 +3,7 @@ import * as assert from "assert"; import * as AsyncRetry from "async-retry"; import { IDebugProvider } from "../IDebugProvider"; -import logger from "../../../utils/logger"; +import { loggerDebug } from "../../../utils/logger"; import { JDWP } from "./jdwp"; export class JavaDebugProvider extends IDebugProvider { @@ -68,7 +68,7 @@ export class JavaDebugProvider extends IDebugProvider { const result = await jdwp.getVersion(); assert(result); - logger.debug("jdwp version", result); + loggerDebug.debug("jdwp version", result); await jdwp.destroy(); } diff --git a/src/main/debug/provider/pythonDebugProvider.ts b/src/main/debug/provider/pythonDebugProvider.ts index f6df1a42..e9a9f1e2 100644 --- a/src/main/debug/provider/pythonDebugProvider.ts +++ b/src/main/debug/provider/pythonDebugProvider.ts @@ -2,7 +2,7 @@ import { DebugConfiguration } from "vscode"; import * as assert from "assert"; import { IDebugProvider } from "./IDebugProvider"; -import logger from "../../utils/logger"; +import logger, { loggerDebug } from "../../utils/logger"; import { SocketDebugClient } from "../SocketDebugClient"; export class PythonDebugProvider extends IDebugProvider { @@ -54,7 +54,7 @@ export class PythonDebugProvider extends IDebugProvider { assert(result.success); - logger.debug("debugpy debugpySystemInfo", result); + loggerDebug.debug("debugpy debugpySystemInfo", result); debugClient.destroy(); } diff --git a/src/main/debug/remoteTerminal.ts b/src/main/debug/remoteTerminal.ts index d8d0045d..8dad3247 100644 --- a/src/main/debug/remoteTerminal.ts +++ b/src/main/debug/remoteTerminal.ts @@ -4,6 +4,7 @@ import { ChildProcessWithoutNullStreams, spawn } from "child_process"; import host from "../host"; import logger from "../utils/logger"; import { getExecCommand } from "../ctl/shell"; + type SpawnClose = (code: number, signal: NodeJS.Signals) => void; type RemoteTerminalType = { terminal: { @@ -79,9 +80,9 @@ export class RemoteTerminal implements vscode.Terminal { host.log(log); logger.info(log); - const proc = spawn(command, [], { - shell: true, - }); + const commands = command.split(" "); + + const proc = spawn(commands.shift(), commands); proc.stdout.on("data", (data: Buffer) => { const str = data.toString(); diff --git a/src/main/domain/IDevSpaceInfo.ts b/src/main/domain/IDevSpaceInfo.ts index 65c82db1..500a359f 100644 --- a/src/main/domain/IDevSpaceInfo.ts +++ b/src/main/domain/IDevSpaceInfo.ts @@ -10,5 +10,6 @@ export interface IDevSpaceInfo { storageClass: string; devStartAppendCommand: Array; spaceOwnType?: "Viewer" | "Owner" | string; + isAsleep?: "asleep" | "wakeup"; [key: string]: any; } diff --git a/src/main/domain/IRootNode.ts b/src/main/domain/IRootNode.ts index 2398a65a..803a6636 100644 --- a/src/main/domain/IRootNode.ts +++ b/src/main/domain/IRootNode.ts @@ -1,21 +1,20 @@ -import { IUserInfo } from "./IUserInfo"; - -import { IDevSpaceInfo } from "./IDevSpaceInfo"; import { IV2ApplicationInfo } from "./IV2ApplicationInfo"; import { ClusterSource } from "../common/define"; -import AccountClusterService from "../clusters/AccountCluster"; -import { ClustersState } from "../clusters"; +import { AccountClusterNode, ClustersState } from "../clusters"; +import { IServiceAccountInfo } from "."; +import { LocalClusterNode } from "../clusters/LocalCuster"; export interface IRootNode { - devSpaces: IDevSpaceInfo[]; + serviceAccount?: IServiceAccountInfo; applications: IV2ApplicationInfo[]; clusterSource?: ClusterSource; - accountClusterService?: AccountClusterService; + // accountClusterService?: AccountClusterService; id?: string; createTime?: number; clusterName?: string; - userInfo?: IUserInfo; kubeConfigPath: string; state: ClustersState; + + clusterInfo?: AccountClusterNode | LocalClusterNode; } diff --git a/src/main/domain/IServiceAccountInfo.ts b/src/main/domain/IServiceAccountInfo.ts index 4741d0c3..b99ef3f0 100644 --- a/src/main/domain/IServiceAccountInfo.ts +++ b/src/main/domain/IServiceAccountInfo.ts @@ -4,10 +4,21 @@ export interface IServiceAccountInfo { storageClass: string; privilege: boolean; privilegeType?: "CLUSTER_ADMIN" | "CLUSTER_VIEWER"; + kubeconfigType?: "vcluster"; + virtualCluster?: { + serviceType: "ClusterIP" | "LoadBalancer" | "NodePort"; + servicePort: string; + serviceAddress: string; + serviceNamespace: string; + hostClusterContext: string; + virtualClusterContext: string; + }; namespacePacks: Array<{ spaceId: number; namespace: string; spacename: string; spaceOwnType?: "Viewer" | "Owner" | string; + isAsleep?: boolean; + sleepStatus?: "wakeup" | "asleep"; }>; } diff --git a/src/main/extension.ts b/src/main/extension.ts index 5d85df57..e690a444 100644 --- a/src/main/extension.ts +++ b/src/main/extension.ts @@ -40,7 +40,7 @@ import { BaseNocalhostNode, DeploymentStatus } from "./nodes/types/nodeType"; import NocalhostWebviewPanel from "./webview/NocalhostWebviewPanel"; import TextDocumentContentProvider from "./textDocumentContentProvider"; import { checkVersion } from "./ctl/nhctl"; -import logger from "./utils/logger"; +import logger, { loggerDebug } from "./utils/logger"; import * as fileUtil from "./utils/fileUtil"; import { KubernetesResourceFolder } from "./nodes/abstract/KubernetesResourceFolder"; // import { registerYamlSchemaSupport } from "./yaml/yamlSchema"; @@ -55,6 +55,7 @@ import SyncServiceCommand from "./commands/SyncServiceCommand"; import { ShellExecError } from "./ctl/shell"; import { createSyncManage } from "./component/syncManage"; import { activateNocalhostDebug } from "./debug/nocalhost"; +import { KubeConfigNode } from "./nodes/KubeConfigNode"; // The example uses the file message format. const localize = nls.config({ messageFormat: nls.MessageFormat.file })(); @@ -90,7 +91,8 @@ export async function activate(context: vscode.ExtensionContext) { const node = e.element; if ( node instanceof KubernetesResourceFolder || - node instanceof DevSpaceNode + node instanceof DevSpaceNode || + node instanceof KubeConfigNode ) { state.refreshFolderMap.set(node.getNodeStateId(), true); } @@ -144,7 +146,7 @@ export async function activate(context: vscode.ExtensionContext) { true ); - await state.refreshTree(); + state.refreshTree(false); launchDevSpace(); @@ -159,7 +161,7 @@ function bindEvent() { return; } - logger.debug("refreshTree", value); + loggerDebug.debug("refreshTree", value); state.startAutoRefresh(true); }); diff --git a/src/main/host.ts b/src/main/host.ts index e713f030..0b97f4af 100644 --- a/src/main/host.ts +++ b/src/main/host.ts @@ -15,7 +15,7 @@ export class Host implements vscode.Disposable { 100 ); - private devspaceDisposesMap = new Map< + private devSpaceDisposesMap = new Map< string, Map< string, @@ -46,12 +46,12 @@ export class Host implements vscode.Disposable { this.context.globalState.update(key, state); } - public getGlobalState(key: string) { + public getGlobalState(key: string) { if (!this.context) { throw new Error("not initialized extension"); } - return this.context.globalState.get(key) as any; + return this.context.globalState.get(key) as T; } public removeGlobalState(key: string) { @@ -87,7 +87,7 @@ export class Host implements vscode.Disposable { } public disposeApp(devspaceName: string, id: string) { - const appMap = this.devspaceDisposesMap.get(devspaceName); + const appMap = this.devSpaceDisposesMap.get(devspaceName); if (!appMap) { return; } @@ -105,7 +105,7 @@ export class Host implements vscode.Disposable { } public disposeDevspace(devspaceName: string) { - const appMap = this.devspaceDisposesMap.get(devspaceName); + const appMap = this.devSpaceDisposesMap.get(devspaceName); if (!appMap) { return; } @@ -115,11 +115,11 @@ export class Host implements vscode.Disposable { }); appMap.clear(); - this.devspaceDisposesMap.delete(devspaceName); + this.devSpaceDisposesMap.delete(devspaceName); } public disposeWorkload(devspaceName: string, appId: string, id: string) { - const appMap = this.devspaceDisposesMap.get(devspaceName); + const appMap = this.devSpaceDisposesMap.get(devspaceName); if (!appMap) { return; } @@ -145,10 +145,10 @@ export class Host implements vscode.Disposable { id: string, obj: { dispose: () => any } ) { - let appMap = this.devspaceDisposesMap.get(devspaceName); + let appMap = this.devSpaceDisposesMap.get(devspaceName); if (!appMap) { appMap = new Map(); - this.devspaceDisposesMap.set(devspaceName, appMap); + this.devSpaceDisposesMap.set(devspaceName, appMap); } let workloadMap = appMap.get(appId); if (!workloadMap) { @@ -315,10 +315,10 @@ export class Host implements vscode.Disposable { this.statusBar.dispose(); this.outputChannel.dispose(); - this.devspaceDisposesMap.forEach((m, key) => { + this.devSpaceDisposesMap.forEach((m, key) => { this.disposeDevspace(key); }); - this.devspaceDisposesMap = new Map(); + this.devSpaceDisposesMap = new Map(); } getCurrentRootPath() { diff --git a/src/main/nodes/DevSpaceNode.ts b/src/main/nodes/DevSpaceNode.ts index e7ead2fd..de34ecde 100644 --- a/src/main/nodes/DevSpaceNode.ts +++ b/src/main/nodes/DevSpaceNode.ts @@ -2,7 +2,7 @@ import { difference } from "lodash"; import * as vscode from "vscode"; import { ClusterSource } from "../common/define"; import * as nhctl from "../ctl/nhctl"; -import { IDevSpaceInfo, IV2ApplicationInfo } from "../domain"; +import { IDevSpaceInfo, IRootNode, IV2ApplicationInfo } from "../domain"; import state from "../state"; import { resolveVSCodeUri } from "../utils/fileUtil"; import { NocalhostFolderNode } from "./abstract/NocalhostFolderNode"; @@ -17,6 +17,15 @@ import { StorageFolder } from "./storage/StorageFolder"; import { BaseNocalhostNode } from "./types/nodeType"; import { WorkloadFolderNode } from "./workloads/WorkloadFolderNode"; +export function getDevSpaceLabel(info: IDevSpaceInfo) { + let { spaceName: label, namespace } = info; + + if (label && namespace !== label) { + label += `(${namespace})`; + } + + return label || namespace; +} export class DevSpaceNode extends NocalhostFolderNode implements RefreshData { public label: string; public type = NodeType.devSpace; @@ -32,7 +41,6 @@ export class DevSpaceNode extends NocalhostFolderNode implements RefreshData { constructor( parent: BaseNocalhostNode, - label: string, info: IDevSpaceInfo, applications: Array, clusterSource: ClusterSource @@ -45,10 +53,7 @@ export class DevSpaceNode extends NocalhostFolderNode implements RefreshData { this.installedApps = []; this.clusterSource = clusterSource; - if (label && info.namespace !== label) { - label += `(${info.namespace})`; - } - this.label = label || info.namespace; + this.label = getDevSpaceLabel(info); state.setNode(this.getNodeStateId(), this); } @@ -259,7 +264,9 @@ export class DevSpaceNode extends NocalhostFolderNode implements RefreshData { treeItem.iconPath = resolveVSCodeUri("loading.gif"); } else { const iconName = - this.info.spaceOwnType === "Viewer" + this.info.isAsleep === "asleep" + ? "namespace_sleep.svg" + : this.info.spaceOwnType === "Viewer" ? "devspace_viewer.svg" : "devspace.svg"; treeItem.iconPath = resolveVSCodeUri(iconName); @@ -267,10 +274,27 @@ export class DevSpaceNode extends NocalhostFolderNode implements RefreshData { treeItem.contextValue = this.getSpaceOwnTypeContextValue( `devspace-${ - this.clusterSource === ClusterSource.local ? "local" : "server" + this.clusterSource === ClusterSource.local + ? "local" + : this.info.isAsleep === "asleep" + ? "server-sleeping" + : this.info.isAsleep === "wakeup" + ? "server-unsleeping" + : "server" }` ); + if ("rootNode" in this.parent) { + const { rootNode } = (this.parent as unknown) as { rootNode: IRootNode }; + + if ( + rootNode.clusterSource === ClusterSource.server && + rootNode.serviceAccount.kubeconfigType === "vcluster" + ) { + treeItem.contextValue += "-vcluster"; + } + } + return Promise.resolve(treeItem); } diff --git a/src/main/nodes/KubeConfigNode.ts b/src/main/nodes/KubeConfigNode.ts index e06784bd..61374702 100644 --- a/src/main/nodes/KubeConfigNode.ts +++ b/src/main/nodes/KubeConfigNode.ts @@ -1,25 +1,30 @@ import * as vscode from "vscode"; -import { orderBy } from "lodash"; -import { HELM_NH_CONFIG_DIR, NOCALHOST } from "../constants"; +import { difference, orderBy } from "lodash"; +import * as fs from "fs"; +import * as yaml from "yaml"; + +import { NOCALHOST } from "../constants"; import state from "../state"; -import AccountClusterService from "../clusters/AccountCluster"; +import AccountClusterService, { + AccountClusterNode, +} from "../clusters/AccountCluster"; import { ID_SPLIT } from "./nodeContants"; -import * as path from "path"; import { ClusterSource } from "../common/define"; import { BaseNocalhostNode } from "./types/nodeType"; import { NocalhostFolderNode } from "./abstract/NocalhostFolderNode"; -import { resolveVSCodeUri, writeFileLock } from "../utils/fileUtil"; -import { DevSpaceNode } from "./DevSpaceNode"; -import { IUserInfo, IDevSpaceInfo, IV2ApplicationInfo } from "../domain"; +import { NocalhostRootNode } from "./NocalhostRootNode"; +import { isExistSync, resolveVSCodeUri } from "../utils/fileUtil"; +import { IV2ApplicationInfo, IRootNode, IDevSpaceInfo } from "../domain"; import { ClustersState } from "../clusters"; +import host from "../host"; +import { getAllNamespace } from "../ctl/nhctl"; +import { DevSpaceNode, getDevSpaceLabel } from "./DevSpaceNode"; +import { LocalClusterNode } from "../clusters/LocalCuster"; export class KubeConfigNode extends NocalhostFolderNode { public label: string; - parent: BaseNocalhostNode = null; public type = "KUBECONFIG"; - public devSpaceInfos: IDevSpaceInfo[]; - public userInfo: IUserInfo; public clusterSource: ClusterSource; public applications: Array; public installedApps: { @@ -28,47 +33,157 @@ export class KubeConfigNode extends NocalhostFolderNode { }[] = []; public id: string; public kubeConfigPath: string; - public accountClusterService: AccountClusterService; private state: ClustersState; - constructor(props: { - id: string; - label: string; - devSpaceInfos: IDevSpaceInfo[]; - applications: Array; - clusterSource: ClusterSource; - kubeConfigPath: string; - userInfo: IUserInfo; - accountClusterService?: AccountClusterService; - state: ClustersState; - }) { + constructor( + id: string, + public parent: BaseNocalhostNode, + label: string, + public rootNode: IRootNode + ) { super(); - const { - id, - label, - devSpaceInfos, - applications, - clusterSource, - kubeConfigPath, - userInfo, - accountClusterService, - } = props; + + const { applications, clusterSource, kubeConfigPath } = rootNode; + this.id = id; this.clusterSource = clusterSource; - this.label = - label || (devSpaceInfos.length > 0 ? devSpaceInfos[0].namespace : ""); - this.devSpaceInfos = devSpaceInfos; + + this.label = label; this.applications = applications; this.installedApps = []; this.kubeConfigPath = kubeConfigPath; - this.userInfo = userInfo; - this.accountClusterService = accountClusterService; - this.state = props.state; + this.state = rootNode.state; state.setNode(this.getNodeStateId(), this); } - updateData(): any { - return []; + + getClusterNode() { + return this.rootNode.clusterInfo as T; + } + + get accountClusterService() { + if (this.clusterSource === ClusterSource.local) { + return null; + } + + return new AccountClusterService( + this.rootNode.clusterInfo as AccountClusterNode + ); + } + async updateData() { + if (this.state.code !== 200) { + return []; + } + + let devSpaces = Array.of(); + const { kubeConfigPath } = this; + + if (this.clusterSource === ClusterSource.local) { + if (!isExistSync(kubeConfigPath)) { + host.log(`no such file or directory: ${kubeConfigPath}`); + return; + } + + const kubeStr = fs.readFileSync(kubeConfigPath); + const kubeConfigObj = yaml.parse(`${kubeStr}`); + const contexts = kubeConfigObj["contexts"]; + if (!contexts || contexts.length === 0) { + return; + } + + let namespace = contexts[0]["context"]["namespace"] || ""; + + if (kubeConfigObj["current-context"]) { + const currentContext = contexts.find( + (it: any) => it.name === kubeConfigObj["current-context"] + ); + if (currentContext) { + namespace = currentContext.context.namespace; + } + } + + devSpaces = await getAllNamespace({ + kubeConfigPath: kubeConfigPath, + namespace, + }); + } else { + const sa = this.rootNode.serviceAccount; + + if (sa.privilege) { + devSpaces = await getAllNamespace({ + kubeConfigPath: kubeConfigPath, + namespace: "default", + }); + + for (const dev of devSpaces) { + dev.storageClass = sa.storageClass; + dev.devStartAppendCommand = [ + "--priority-class", + "nocalhost-container-critical", + ]; + dev.kubeconfig = sa.kubeconfig; + + const ns = sa.namespacePacks?.find( + (ns) => ns.namespace === dev.namespace + ); + + dev.spaceId = ns?.spaceId; + dev.spaceName = ns?.spacename; + dev.isAsleep = ns?.sleepStatus; + + if (sa.privilegeType === "CLUSTER_ADMIN") { + dev.spaceOwnType = "Owner"; + } else if (sa.privilegeType === "CLUSTER_VIEWER") { + dev.spaceOwnType = ns?.spaceOwnType ?? "Viewer"; + } + } + } else { + for (const ns of sa.namespacePacks) { + const devInfo: IDevSpaceInfo = { + id: ns.spaceId, + spaceName: ns.spacename, + namespace: ns.namespace, + kubeconfig: sa.kubeconfig, + clusterId: sa.clusterId, + storageClass: sa.storageClass, + spaceOwnType: ns.spaceOwnType, + isAsleep: ns.sleepStatus, + devStartAppendCommand: [ + "--priority-class", + "nocalhost-container-critical", + ], + }; + devSpaces.push(devInfo); + } + } + } + + this.cleanDiffDevSpace(devSpaces); + + state.setData(this.getNodeStateId(), devSpaces); + + return devSpaces; + } + + private async cleanDiffDevSpace(resources: Array) { + const old = state.getData>(this.getNodeStateId()); + + if (old && old.length && resources.length) { + const getId = (devSpace: IDevSpaceInfo) => + `${this.getNodeStateId()}${ID_SPLIT}${getDevSpaceLabel(devSpace)}`; + + const diff: string[] = difference(old.map(getId), resources.map(getId)); + + if (diff.length) { + diff.forEach((id) => { + state.disposeNode({ + getNodeStateId() { + return id; + }, + }); + }); + } + } } public getKubeConfigPath() { @@ -76,43 +191,30 @@ export class KubeConfigNode extends NocalhostFolderNode { } async getChildren(parent?: BaseNocalhostNode): Promise { - let res = { - devSpaces: this.devSpaceInfos, - applications: this.applications, - }; - const devs: (DevSpaceNode & { order?: boolean; isSpace?: boolean })[] = []; - - res.applications.forEach(async (app) => { - let context = app.context; - const obj = { - nocalhostConfig: "", - }; - if (context) { - let jsonObj = JSON.parse(context); - obj.nocalhostConfig = jsonObj["nocalhostConfig"]; - } + let devSpaces = Array.of(); - const nhConfigPath = path.resolve(HELM_NH_CONFIG_DIR, `${app.id}_config`); - await writeFileLock(nhConfigPath, obj.nocalhostConfig || ""); - }); - for (const d of res.devSpaces) { - const node = new DevSpaceNode( - this, - d.spaceName, - d, - res.applications, - this.clusterSource - ); - devs.push( - Object.assign(node, { - order: d.spaceOwnType !== "Viewer", - isSpace: d.spaceId > 0, - }) - ); + if (this.state.code !== 200) { + return []; } + devSpaces = state.getData>(this.getNodeStateId()); + + if (!devSpaces) { + devSpaces = await this.updateData(); + } + + const devSpace = devSpaces.map((devSpace) => { + return Object.assign( + new DevSpaceNode(this, devSpace, this.applications, this.clusterSource), + { + order: devSpace.spaceOwnType !== "Viewer", + isSpace: devSpace.spaceId > 0, + } + ); + }); + return orderBy( - devs, + devSpace, ["order", "isSpace", "label"], ["desc", "desc", "asc"] ); @@ -131,16 +233,20 @@ export class KubeConfigNode extends NocalhostFolderNode { }`; if (this.clusterSource === ClusterSource.server) { - const { username, baseUrl } = this.accountClusterService.loginInfo; + const { username, baseUrl } = (this.rootNode + .clusterInfo as AccountClusterNode).loginInfo; treeItem.tooltip = `${username} [${baseUrl}]`; } + const clusterType = this.clusterType; + treeItem.description = "Active"; - treeItem.iconPath = resolveVSCodeUri("cluster_active.svg"); + treeItem.iconPath = resolveVSCodeUri(`${clusterType}_active.svg`); if (this.state.code !== 200) { - treeItem.iconPath = resolveVSCodeUri("cluster_warning.svg"); + treeItem.tooltip = this.state.info; + treeItem.iconPath = resolveVSCodeUri(`${clusterType}_warning.svg`); if (this.state.info !== "No clusters") { treeItem.tooltip = this.state.info; @@ -154,6 +260,16 @@ export class KubeConfigNode extends NocalhostFolderNode { return Promise.resolve(treeItem); } + get clusterType(): "vcluster" | "cluster" { + if ( + this.clusterSource === ClusterSource.server && + this.rootNode.serviceAccount?.kubeconfigType === "vcluster" + ) { + return "vcluster"; + } + return "cluster"; + } + getNodeStateId(): string { return `${this.id}${NOCALHOST}${ID_SPLIT}${this.label}`; } diff --git a/src/main/nodes/NocalhostRootNode.ts b/src/main/nodes/NocalhostRootNode.ts index e21e8e1f..8dce7c5c 100644 --- a/src/main/nodes/NocalhostRootNode.ts +++ b/src/main/nodes/NocalhostRootNode.ts @@ -1,11 +1,16 @@ -import { difference, get, orderBy } from "lodash"; +import * as assert from "assert"; +import { get, orderBy } from "lodash"; import * as vscode from "vscode"; import { sortResources } from "../clusters"; import AccountClusterService, { AccountClusterNode, + buildRootNodeForAccountCluster, } from "../clusters/AccountCluster"; import { LoginInfo } from "../clusters/interface"; -import LocalCusterService, { LocalClusterNode } from "../clusters/LocalCuster"; +import LocalCusterService, { + buildRootNodeForLocalCluster, + LocalClusterNode, +} from "../clusters/LocalCuster"; import { ClusterSource } from "../common/define"; import { GLOBAL_TIMEOUT, @@ -24,7 +29,9 @@ import { KubeConfigNode } from "./KubeConfigNode"; import { ROOT } from "./nodeContants"; import { BaseNocalhostNode } from "./types/nodeType"; -export async function getClusterName(res: IRootNode) { +export async function getClusterName( + res: Pick +) { if (!res.kubeConfigPath) { return "unknown"; } @@ -60,13 +67,15 @@ export class NocalhostRootNode implements BaseNocalhostNode { let nodes = await asyncLimit( localClusterNodes, (localCluster) => { - if ( - token?.isCancellationRequested || - !isExistSync(localCluster.filePath) - ) { + if (token?.isCancellationRequested) { return Promise.reject(); } + assert( + isExistSync(localCluster.filePath), + `kubeconfig not exist:${localCluster.filePath}` + ); + return LocalCusterService.getLocalClusterRootNode(localCluster); }, GLOBAL_TIMEOUT @@ -80,20 +89,10 @@ export class NocalhostRootNode implements BaseNocalhostNode { logger.error("get localCluster error", result.reason, localCluster); - const root: IRootNode = { - ...localCluster, - clusterSource: ClusterSource.local, - clusterName: localCluster.clusterNickName, - kubeConfigPath: localCluster.filePath, - devSpaces: [], - applications: [], - state: { - code: 201, - info: result.reason, - }, - }; - - return root; + return buildRootNodeForLocalCluster(localCluster, { + code: 201, + info: result.reason, + }); }); }); @@ -135,20 +134,12 @@ export class NocalhostRootNode implements BaseNocalhostNode { logger.error("get serverCluster error", result.reason, account); - const rootNode: IRootNode = { - ...account, - clusterSource: ClusterSource.server, - accountClusterService: new AccountClusterService(account.loginInfo), - devSpaces: [], - applications: [], - kubeConfigPath: null, - state: { + return [ + buildRootNodeForAccountCluster(account, { code: 201, info: result.reason?.message, - }, - }; - - return [rootNode]; + }), + ]; }) .flat(1); }); @@ -177,8 +168,6 @@ export class NocalhostRootNode implements BaseNocalhostNode { let resultData = sortResources(data.flat(1)); - await this.cleanDiffDevSpace(resultData); - state.setData(this.getNodeStateId(), resultData, isInit); return resultData; @@ -186,7 +175,7 @@ export class NocalhostRootNode implements BaseNocalhostNode { public async addCluster(node: AccountClusterNode | LocalClusterNode) { let addResources: IRootNode[]; - let resources = state.getData(this.getNodeStateId()) as IRootNode[]; + let resources = state.getData>(this.getNodeStateId()); if (node instanceof LocalClusterNode) { if ( @@ -200,18 +189,9 @@ export class NocalhostRootNode implements BaseNocalhostNode { } addResources = [ - await LocalCusterService.getLocalClusterRootNode(node).catch((err) => { - return { - ...node, - kubeConfigPath: node.filePath, - devSpaces: [], - applications: [], - state: { - code: 201, - info: err.message, - }, - }; - }), + await LocalCusterService.getLocalClusterRootNode(node).catch((err) => + buildRootNodeForLocalCluster(node, { code: 201, info: err.message }) + ), ]; } else { if ( @@ -225,22 +205,12 @@ export class NocalhostRootNode implements BaseNocalhostNode { addResources = await AccountClusterService.getServerClusterRootNodes( node - ).catch((err) => { - return [ - { - ...node, - kubeConfigPath: null, - clusterSource: ClusterSource.server, - accountClusterService: new AccountClusterService(node.loginInfo), - devSpaces: [], - applications: [], - state: { - code: 201, - info: err.message, - }, - }, - ]; - }); + ).catch((err) => [ + buildRootNodeForAccountCluster(node, { + code: 201, + info: err.message, + }), + ]); } if (resources) { resources = resources.concat(addResources); @@ -251,8 +221,8 @@ export class NocalhostRootNode implements BaseNocalhostNode { state.setData(this.getNodeStateId(), resources, false); } - public async deleteCluster(info: LoginInfo | string) { - let resources = state.getData(this.getNodeStateId()) as IRootNode[]; + public deleteCluster(info: LoginInfo | string) { + let resources = state.getData>(this.getNodeStateId()); if (resources) { if (typeof info === "string") { @@ -264,59 +234,25 @@ export class NocalhostRootNode implements BaseNocalhostNode { ) ); } else { - resources = resources.filter((item: any) => { + resources = resources.filter((item) => { if (item.clusterSource === ClusterSource.local) { return true; } - const node = item as KubeConfigNode; - const { username, baseUrl } = node.accountClusterService?.loginInfo; + const { + username, + baseUrl, + } = (item.clusterInfo as AccountClusterNode).loginInfo; return !(username === info.username && baseUrl === info.baseUrl); }); } resources = sortResources(resources); - await this.cleanDiffDevSpace(resources); - state.setData(this.getNodeStateId(), resources, false); } } - private async cleanDiffDevSpace(resources: IRootNode[]) { - const old = state.getData(this.getNodeStateId()) as IRootNode[]; - - if (old && old.length && resources.length) { - const getId = async (resource: IRootNode) => { - const kubeconfigNode = await this.getKubeConfigNode(resource); - const children = await kubeconfigNode.getChildren(); - - let arrayId = [kubeconfigNode.getNodeStateId()]; - - arrayId = arrayId.concat( - children.map((child) => child.getNodeStateId()) - ); - - return arrayId; - }; - - const oldId = (await Promise.all(old.map(getId))).flat(1); - const newId = (await Promise.all(resources.map(getId))).flat(1); - - const diff: string[] = difference(oldId, newId); - - if (diff.length) { - diff.forEach((id) => { - state.disposeNode({ - getNodeStateId() { - return id; - }, - }); - }); - } - } - } - public label: string = NOCALHOST; public type = ROOT; constructor(public parent: BaseNocalhostNode | null) { @@ -330,17 +266,7 @@ export class NocalhostRootNode implements BaseNocalhostNode { async getKubeConfigNode(res: IRootNode) { const clusterName = await getClusterName(res); - return new KubeConfigNode({ - id: res.id, - label: clusterName, - kubeConfigPath: res.kubeConfigPath, - devSpaceInfos: res.devSpaces, - applications: res.applications, - userInfo: res.userInfo, - clusterSource: res.clusterSource, - accountClusterService: res.accountClusterService, - state: res.state, - }); + return new KubeConfigNode(res.id, this, clusterName, res); } async getChildren( parent?: BaseNocalhostNode @@ -368,18 +294,11 @@ export class NocalhostRootNode implements BaseNocalhostNode { if (result.reason instanceof Error) { info = result.reason.message; - logger.error("get serverCluster error", result.reason, res.userInfo); + logger.error("get cluster error", result.reason, res.clusterInfo); } - return new KubeConfigNode({ - id: res.id, - label: res.clusterName, - kubeConfigPath: res.kubeConfigPath, - devSpaceInfos: res.devSpaces, - applications: res.applications, - userInfo: res.userInfo, - clusterSource: res.clusterSource, - accountClusterService: res.accountClusterService, + return new KubeConfigNode(res.id, this, res.clusterName, { + ...res, state: { code: 201, info, diff --git a/src/main/nodes/types/nodeType.ts b/src/main/nodes/types/nodeType.ts index 196e21e9..89a88356 100644 --- a/src/main/nodes/types/nodeType.ts +++ b/src/main/nodes/types/nodeType.ts @@ -1,6 +1,5 @@ -import AccountClusterService from "./../../clusters/AccountCluster"; import * as vscode from "vscode"; - +import { IRootNode } from "../../domain"; export interface AppInfo { name: string; releasename: string; @@ -60,13 +59,14 @@ export interface SvcProfile { export interface BaseNocalhostNode { label: string; type: string; - accountClusterService?: AccountClusterService; hasInit?: boolean; resourceType?: string; parent: BaseNocalhostNode | undefined | null; + rootNode?: IRootNode; updateData?: ( init?: boolean, - token?: vscode.CancellationToken + token?: vscode.CancellationToken, + isCancel?: () => boolean ) => Promise; getNodeStateId(): string; getChildren( diff --git a/src/main/nodes/workloads/KubernetesResourceDevMode.ts b/src/main/nodes/workloads/KubernetesResourceDevMode.ts index 5929f5fa..ac092663 100644 --- a/src/main/nodes/workloads/KubernetesResourceDevMode.ts +++ b/src/main/nodes/workloads/KubernetesResourceDevMode.ts @@ -40,8 +40,10 @@ export const kubernetesResourceDevMode = (resourceNode: any) => ( }); return this.sortResource(result); }; - - prototype.updateData = async function (isInit?: boolean): Promise { + prototype.updateData = async function ( + isInit?: boolean, + token?: vscode.CancellationToken + ): Promise { const appNode = this.getAppNode(); // description const list: INhCtlGetResult[] = @@ -60,7 +62,11 @@ export const kubernetesResourceDevMode = (resourceNode: any) => ( const obj = { resource: list, }; - state.setData(this.getNodeStateId(), obj, isInit); + + if (!token?.isCancellationRequested) { + state.setData(this.getNodeStateId(), obj, isInit); + } + return obj; }; }; diff --git a/src/main/state.ts b/src/main/state.ts index b9bbeba0..8d7b1bfd 100644 --- a/src/main/state.ts +++ b/src/main/state.ts @@ -2,7 +2,7 @@ import * as vscode from "vscode"; import { isEqual } from "lodash"; import { isExistCluster } from "./clusters/utils"; import { BaseNocalhostNode } from "./nodes/types/nodeType"; -import logger from "./utils/logger"; +import logger, { loggerDebug } from "./utils/logger"; import { asyncLimit } from "./utils"; import { GLOBAL_TIMEOUT } from "./constants"; @@ -11,7 +11,7 @@ class State { private stateMap = new Map(); private nodeMap = new Map(); - private dataMap = new Map(); + private dataMap = new Map(); public refreshFolderMap = new Map(); private renderTime: NodeJS.Timeout; @@ -56,8 +56,8 @@ class State { } } - public getData(id: string) { - return this.dataMap.get(id); + public getData(id: string) { + return this.dataMap.get(id) as T; } private autoRefreshTimeId: NodeJS.Timeout | null = null; @@ -87,11 +87,13 @@ class State { const refresh = async () => { const { token } = action; - let time = Date.now(); try { const rootNode = this.getNode("Nocalhost") as BaseNocalhostNode; if (rootNode) { + if (rootNode.hasInit === false) { + return; + } await rootNode.updateData(null, token).catch(() => {}); } @@ -101,7 +103,7 @@ class State { const node = this.getNode(id) as BaseNocalhostNode; if (!token.isCancellationRequested && node && expanded) { - return node.updateData(); + return node.updateData(null, token); } return Promise.resolve(); @@ -120,10 +122,6 @@ class State { await this.startAutoRefresh(); }, 10 * 1000); } - - logger.info( - `refresh size:${this.refreshFolderMap.size} time:${Date.now() - time}` - ); }; this.cancellationToken = action; @@ -259,18 +257,25 @@ class State { for (let key of this.stateMap.keys()) { if (key.startsWith(stateId)) { - logger.debug("stateMap", key); + loggerDebug.debug("stateMap", key); this.stateMap.delete(key); } } + for (let key of this.dataMap.keys()) { + if (key.startsWith(stateId)) { + loggerDebug.debug("dataMap", key); + this.dataMap.delete(key); + } + } + if (!deleteRefresh) { return; } for (let key of this.refreshFolderMap.keys()) { if (key.startsWith(stateId)) { - logger.debug("cleanAutoRefresh", key); + loggerDebug.debug("cleanAutoRefresh", key); this.refreshFolderMap.delete(key); } } diff --git a/src/main/utils/logger.ts b/src/main/utils/logger.ts index da74ef5b..2915c538 100644 --- a/src/main/utils/logger.ts +++ b/src/main/utils/logger.ts @@ -3,25 +3,34 @@ import * as path from "path"; import { PLUGIN_CONFIG_DIR } from "../constants"; const loggerPath = path.resolve(PLUGIN_CONFIG_DIR, "vsc_log"); +const debugPath = path.resolve(PLUGIN_CONFIG_DIR, "vsc_log.debug"); + +const defaultAppender: log4js.Appender = { + type: "dateFile", + filename: loggerPath, + maxLogSize: 10485760, + daysToKeep: 3, + pattern: "yyyy-MM-dd", + backups: 3, + compress: false, +}; log4js.configure({ appenders: { - everything: { - type: "dateFile", - filename: loggerPath, - maxLogSize: 10485760, - daysToKeep: 3, - pattern: "yyyy-MM-dd", - backups: 3, - compress: false, - }, + default: defaultAppender, + debug: { ...defaultAppender, filename: debugPath }, }, categories: { - default: { appenders: ["everything"], level: "debug" }, + default: { appenders: ["default"], level: "info" }, + debug: { appenders: ["debug"], level: "debug" }, }, }); const logger = log4js.getLogger(); -logger.level = "debug"; + +const loggerDebug = log4js.getLogger("debug"); +loggerDebug.level = "debug"; export default logger; + +export { loggerDebug }; diff --git a/yarn.lock b/yarn.lock index f0688025..54b219a5 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4054,15 +4054,10 @@ flush-write-stream@^1.0.0: inherits "^2.0.3" readable-stream "^2.3.6" -follow-redirects@^1.10.0: - version "1.13.0" - resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.13.0.tgz#b42e8d93a2a7eea5ed88633676d6597bc8e384db" - integrity sha512-aq6gF1BEKje4a9i9+5jimNFIpq4Q1WiwBToeRK5NvZBd/TRsmW8BsJfOEGkr76TbOyPVD3OVDN910EcUNtRYEA== - -follow-redirects@^1.13.2: - version "1.14.2" - resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.14.2.tgz#cecb825047c00f5e66b142f90fed4f515dec789b" - integrity sha512-yLR6WaE2lbF0x4K2qE2p9PEXKLDjUjnR/xmjS3wHAYxtlsI9MLLBJUZirAHKzUZDGLxje7w/cXR49WOUo4rbsA== +follow-redirects@^1.10.0, follow-redirects@^1.13.2: + version "1.14.7" + resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.14.7.tgz#2004c02eb9436eee9a21446a6477debf17e81685" + integrity sha512-+hbxoLbFMbRKDwohX8GkTataGqO6Jb7jGwpAlwgy2bIz25XtRm7KEzJM76R1WiNT5SwZkX4Y75SwBolkpmE7iQ== for-in@^1.0.2: version "1.0.2"