From 0c1291da71ce428fc5578b5db2f84f87f9edc270 Mon Sep 17 00:00:00 2001 From: Jan-David Wiederstein <141321444+Datata1@users.noreply.github.com> Date: Tue, 18 Jun 2024 14:40:15 +0200 Subject: [PATCH 01/12] Update .vscodeignore --- .vscodeignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.vscodeignore b/.vscodeignore index b058495..54520f0 100644 --- a/.vscodeignore +++ b/.vscodeignore @@ -5,6 +5,8 @@ node_modules/** .yarnrc webpack.config.js vsc-extension-quickstart.md +.codesphere-internal +server **/tsconfig.json **/.eslintrc.json **/*.map From 8d4a6da59a9028424725dbd7055c734c9981fb4c Mon Sep 17 00:00:00 2001 From: jdwiederstein01 Date: Thu, 21 Nov 2024 18:16:46 +0100 Subject: [PATCH 02/12] change dependencies --- package-lock.json | 145 ++++++++++++++++++++++++++++++++++++++++++++-- package.json | 2 +- 2 files changed, 142 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index 85c4de2..eb851a7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,7 +8,7 @@ "name": "codesphere", "version": "0.1.11", "dependencies": { - "@vscode/vsce": "^2.26.1", + "@vscode/vsce": "^2.32.0", "ansi-to-html": "^0.7.2", "axios": "^1.6.8", "bufferutil": "^4.0.8", @@ -1212,11 +1212,13 @@ } }, "node_modules/@vscode/vsce": { - "version": "2.26.1", - "resolved": "https://registry.npmjs.org/@vscode/vsce/-/vsce-2.26.1.tgz", - "integrity": "sha512-QOG6Ht7V93nhwcBxPWcG33UK0qDGEoJdg0xtVeaTN27W6PGdMJUJGTPhB/sNHUIFKwvwzv/zMAHvDgMNXbcwlA==", + "version": "2.32.0", + "resolved": "https://registry.npmjs.org/@vscode/vsce/-/vsce-2.32.0.tgz", + "integrity": "sha512-3EFJfsgrSftIqt3EtdRcAygy/OJ3hstyI1cDmIgkU9CFZW5C+3djr6mfosndCUqcVYuyjmxOK1xmFp/Bq7+NIg==", + "license": "MIT", "dependencies": { "@azure/identity": "^4.1.0", + "@vscode/vsce-sign": "^2.0.0", "azure-devops-node-api": "^12.5.0", "chalk": "^2.4.2", "cheerio": "^1.0.0-rc.9", @@ -1250,6 +1252,141 @@ "keytar": "^7.7.0" } }, + "node_modules/@vscode/vsce-sign": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@vscode/vsce-sign/-/vsce-sign-2.0.5.tgz", + "integrity": "sha512-GfYWrsT/vypTMDMgWDm75iDmAOMe7F71sZECJ+Ws6/xyIfmB3ELVnVN+LwMFAvmXY+e6eWhR2EzNGF/zAhWY3Q==", + "hasInstallScript": true, + "license": "SEE LICENSE IN LICENSE.txt", + "optionalDependencies": { + "@vscode/vsce-sign-alpine-arm64": "2.0.2", + "@vscode/vsce-sign-alpine-x64": "2.0.2", + "@vscode/vsce-sign-darwin-arm64": "2.0.2", + "@vscode/vsce-sign-darwin-x64": "2.0.2", + "@vscode/vsce-sign-linux-arm": "2.0.2", + "@vscode/vsce-sign-linux-arm64": "2.0.2", + "@vscode/vsce-sign-linux-x64": "2.0.2", + "@vscode/vsce-sign-win32-arm64": "2.0.2", + "@vscode/vsce-sign-win32-x64": "2.0.2" + } + }, + "node_modules/@vscode/vsce-sign-alpine-arm64": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@vscode/vsce-sign-alpine-arm64/-/vsce-sign-alpine-arm64-2.0.2.tgz", + "integrity": "sha512-E80YvqhtZCLUv3YAf9+tIbbqoinWLCO/B3j03yQPbjT3ZIHCliKZlsy1peNc4XNZ5uIb87Jn0HWx/ZbPXviuAQ==", + "cpu": [ + "arm64" + ], + "license": "SEE LICENSE IN LICENSE.txt", + "optional": true, + "os": [ + "alpine" + ] + }, + "node_modules/@vscode/vsce-sign-alpine-x64": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@vscode/vsce-sign-alpine-x64/-/vsce-sign-alpine-x64-2.0.2.tgz", + "integrity": "sha512-n1WC15MSMvTaeJ5KjWCzo0nzjydwxLyoHiMJHu1Ov0VWTZiddasmOQHekA47tFRycnt4FsQrlkSCTdgHppn6bw==", + "cpu": [ + "x64" + ], + "license": "SEE LICENSE IN LICENSE.txt", + "optional": true, + "os": [ + "alpine" + ] + }, + "node_modules/@vscode/vsce-sign-darwin-arm64": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@vscode/vsce-sign-darwin-arm64/-/vsce-sign-darwin-arm64-2.0.2.tgz", + "integrity": "sha512-rz8F4pMcxPj8fjKAJIfkUT8ycG9CjIp888VY/6pq6cuI2qEzQ0+b5p3xb74CJnBbSC0p2eRVoe+WgNCAxCLtzQ==", + "cpu": [ + "arm64" + ], + "license": "SEE LICENSE IN LICENSE.txt", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@vscode/vsce-sign-darwin-x64": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@vscode/vsce-sign-darwin-x64/-/vsce-sign-darwin-x64-2.0.2.tgz", + "integrity": "sha512-MCjPrQ5MY/QVoZ6n0D92jcRb7eYvxAujG/AH2yM6lI0BspvJQxp0o9s5oiAM9r32r9tkLpiy5s2icsbwefAQIw==", + "cpu": [ + "x64" + ], + "license": "SEE LICENSE IN LICENSE.txt", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@vscode/vsce-sign-linux-arm": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@vscode/vsce-sign-linux-arm/-/vsce-sign-linux-arm-2.0.2.tgz", + "integrity": "sha512-Fkb5jpbfhZKVw3xwR6t7WYfwKZktVGNXdg1m08uEx1anO0oUPUkoQRsNm4QniL3hmfw0ijg00YA6TrxCRkPVOQ==", + "cpu": [ + "arm" + ], + "license": "SEE LICENSE IN LICENSE.txt", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@vscode/vsce-sign-linux-arm64": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@vscode/vsce-sign-linux-arm64/-/vsce-sign-linux-arm64-2.0.2.tgz", + "integrity": "sha512-Ybeu7cA6+/koxszsORXX0OJk9N0GgfHq70Wqi4vv2iJCZvBrOWwcIrxKjvFtwyDgdeQzgPheH5nhLVl5eQy7WA==", + "cpu": [ + "arm64" + ], + "license": "SEE LICENSE IN LICENSE.txt", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@vscode/vsce-sign-linux-x64": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@vscode/vsce-sign-linux-x64/-/vsce-sign-linux-x64-2.0.2.tgz", + "integrity": "sha512-NsPPFVtLaTlVJKOiTnO8Cl78LZNWy0Q8iAg+LlBiCDEgC12Gt4WXOSs2pmcIjDYzj2kY4NwdeN1mBTaujYZaPg==", + "cpu": [ + "x64" + ], + "license": "SEE LICENSE IN LICENSE.txt", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@vscode/vsce-sign-win32-arm64": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@vscode/vsce-sign-win32-arm64/-/vsce-sign-win32-arm64-2.0.2.tgz", + "integrity": "sha512-wPs848ymZ3Ny+Y1Qlyi7mcT6VSigG89FWQnp2qRYCyMhdJxOpA4lDwxzlpL8fG6xC8GjQjGDkwbkWUcCobvksQ==", + "cpu": [ + "arm64" + ], + "license": "SEE LICENSE IN LICENSE.txt", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@vscode/vsce-sign-win32-x64": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@vscode/vsce-sign-win32-x64/-/vsce-sign-win32-x64-2.0.2.tgz", + "integrity": "sha512-pAiRN6qSAhDM5SVOIxgx+2xnoVUePHbRNC7OD2aOR3WltTKxxF25OfpK8h8UQ7A0BuRkSgREbB59DBlFk4iAeg==", + "cpu": [ + "x64" + ], + "license": "SEE LICENSE IN LICENSE.txt", + "optional": true, + "os": [ + "win32" + ] + }, "node_modules/@vscode/vsce/node_modules/brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", diff --git a/package.json b/package.json index 939536d..293100a 100644 --- a/package.json +++ b/package.json @@ -182,7 +182,7 @@ "ws": "^8.16.0" }, "dependencies": { - "@vscode/vsce": "^2.26.1", + "@vscode/vsce": "^2.32.0", "ansi-to-html": "^0.7.2", "axios": "^1.6.8", "bufferutil": "^4.0.8", From 62eeef71fe1aba83c57510731a4834f986139757 Mon Sep 17 00:00:00 2001 From: jdwiederstein01 Date: Thu, 21 Nov 2024 18:17:38 +0100 Subject: [PATCH 03/12] change ci.yaml --- ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ci.yml b/ci.yml index 6b6cf4a..a9e538e 100644 --- a/ci.yml +++ b/ci.yml @@ -7,7 +7,7 @@ prepare: - name: Change node version command: sudo -u admin n 18.18.0 - name: Install Dependencies - command: npm install @vscode/vsce + command: npm i - name: Compile project command: npm run compile - name: bundle project From ca0356c876a915fb22e7a746de357ba3120ad7ce Mon Sep 17 00:00:00 2001 From: "jan-david.wiederstein" Date: Sun, 24 Nov 2024 12:32:56 +0100 Subject: [PATCH 04/12] debug ci-pipeline false initial run stage button status --- src/CiPipelineProvider.ts | 1 + webviews/components/CiPipeline.svelte | 33 ++++++++++++++++++++++++--- 2 files changed, 31 insertions(+), 3 deletions(-) diff --git a/src/CiPipelineProvider.ts b/src/CiPipelineProvider.ts index 57c12ab..c3eb537 100644 --- a/src/CiPipelineProvider.ts +++ b/src/CiPipelineProvider.ts @@ -58,6 +58,7 @@ export class CiPipelineProvider implements vscode.WebviewViewProvider { const ciPipelineCheck = checkCiPipelineStructure(uaSocket, 324); ciPipelineCheck.then((ci: any) => { ciStructure = ci; + console.log("ciStructure: ", JSON.stringify(ciStructure)); this._view?.webview.postMessage({ type: "CIPipelineStages", value: { diff --git a/webviews/components/CiPipeline.svelte b/webviews/components/CiPipeline.svelte index 6f9336f..5f9034c 100644 --- a/webviews/components/CiPipeline.svelte +++ b/webviews/components/CiPipeline.svelte @@ -45,21 +45,30 @@ } step.open = false }); - prepareStageSteps[0].open = true; + + if (stagelength > 0) { + prepareStageSteps[0].open = true + } } if (stage == "test") { stageRunning = [...stageRunning, stage] testStageSate = true; testStageSuccess = ''; stagelength = testStageSteps.length - testStageSteps[0].open = true + + if (stagelength > 0) { + testStageSteps[0].open = true + } } if (stage == "run") { stageRunning = [...stageRunning, stage] runStageSate = true; runStageSuccess = ''; stagelength = runStageSteps.length - runStageSteps[0].open = true + + if (stagelength > 0) { + runStageSteps[0].open = true + } } vscode.postMessage({ type: 'startCiStage', @@ -120,6 +129,10 @@ currentWorkspace = message.value.currentWorkspace; teamId = message.value.teamId; dcId = message.value.dcId; + + console.log("currentWorkspace", currentWorkspace); + console.log("teamId", teamId); + console.log("dcId", dcId); vscode.postMessage({ type: 'getCiPipelineStages', @@ -174,6 +187,7 @@ break; case 'ciPipelineStatus': if (message.value.dynamic) { + console.log("dynamic", message.value.dynamic); switch (message.value.dynamic) { case 'prepare': prepareStageSuccess = message.value.prepare.state; @@ -182,6 +196,8 @@ }); if (prepareStageSuccess === 'running') { prepareStageSate = true; + stageRunning = [...stageRunning, 'prepare'] + } if (prepareStageSuccess === 'success' || prepareStageSuccess === 'failure') { prepareStageSate = false; @@ -194,6 +210,8 @@ }); if (testStageSuccess === 'running') { testStageSate = true; + stageRunning = [...stageRunning, 'test'] + } if (testStageSuccess === 'success' || testStageSuccess === 'failure') { testStageSate = false; @@ -206,6 +224,7 @@ }); if (runStageSuccess === 'running') { runStageSate = true; + stageRunning = [...stageRunning, 'run'] } if (runStageSuccess === 'success' || runStageSuccess === 'failure') { runStageSate = false; @@ -216,12 +235,15 @@ } if (message.value.dynamic === false && message.value.prepare.state){ + console.log("prepare dynamic false", message.value.prepare.state); prepareStageSuccess = message.value.prepare.state; prepareStageSteps.forEach(( step, index) => { Object.assign(step, message.value.prepare.steps[index]); }); if (prepareStageSuccess === 'running') { prepareStageSate = true; + stageRunning = [...stageRunning, 'prepare'] + } if (prepareStageSuccess === 'success' || prepareStageSuccess === 'failure') { prepareStageSate = false; @@ -229,12 +251,15 @@ } if (message.value.dynamic === false && message.value.test.state){ + console.log("test dynamic false", message.value.test.state); testStageSuccess = message.value.test.state; testStageSteps.forEach(( step, index) => { Object.assign(step, message.value.test.steps[index]); }); if (testStageSuccess === 'running') { testStageSate = true; + stageRunning = [...stageRunning, 'test'] + } if (testStageSuccess === 'success' || testStageSuccess === 'failure') { testStageSate = false; @@ -242,12 +267,14 @@ } if (message.value.dynamic === false && message.value.run.state){ + console.log("run dynamic false", message.value.run.state); runStageSuccess = message.value.run.state; runStageSteps.forEach(( step, index) => { Object.assign(step, message.value.run.steps[index]); }); if (runStageSuccess === 'running') { runStageSate = true; + stageRunning = [...stageRunning, 'run'] } if (runStageSuccess === 'success' || runStageSuccess === 'failure') { runStageSate = false; From 3278468a2f8c4c1c7c787180cd6fa4e988458d07 Mon Sep 17 00:00:00 2001 From: "jan-david.wiederstein" Date: Sun, 24 Nov 2024 12:59:02 +0100 Subject: [PATCH 05/12] improve restore window behavior. Only disable it when inside tunnel. This is mandatory, because when off-when-unused workspaces shut down, then the local vscode client cannot initiate properly and we cannot control vscode before the tunnel connection is up. This is a rather sad solution because in THEORY we could control the behavior before vscode's tries to open the tunnel connection, but this API is since 2019 proposal API and it very unlikly that microsoft will change it. Only microsoft extensions are allowed to use proposed API in production vscode and if we want to use such proposed API we need vscode insiders. No casual user has vscode insiders installed. This is the most simple solution to that problem but i dont like that we interfere with global user settings. Why does microsoft allow to change global user settings? I could create an extension and destroy all the user settings of anyone who install my extension. Why is that even possible?! --- src/extension.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/extension.ts b/src/extension.ts index 7473360..163d3b5 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -25,9 +25,6 @@ export function activate(context: vscode.ExtensionContext) { const fileTreeProvider = new FileTreeProvider(rootPath); const ciPipelineProvider = new CiPipelineProvider(context.extensionUri, context); - //TODO: the line below disables vscode to remember the last opened windows. - // change it that it only disables it inside remote tunnels - vscode.workspace.getConfiguration('window').update('restoreWindows', 'none', vscode.ConfigurationTarget.Global); context.subscriptions.push( vscode.window.registerWebviewViewProvider( @@ -100,7 +97,10 @@ export function activate(context: vscode.ExtensionContext) { if (workspaceId !== "" && workspaceId !== "$WORKSPACE_ID") { const pwdUri = vscode.Uri.parse('home/user/app'); + vscode.workspace.getConfiguration('window').update('restoreWindows', 'none', vscode.ConfigurationTarget.Global); vscode.commands.executeCommand('vscode.openFolder', pwdUri); + } else { + vscode.workspace.getConfiguration('window').update('restoreWindows', 'all', vscode.ConfigurationTarget.Global); } }); From ed4380a817bd512b6acb7c17cf5c69fd1b18a9c9 Mon Sep 17 00:00:00 2001 From: "jan-david.wiederstein" Date: Sun, 24 Nov 2024 13:41:48 +0100 Subject: [PATCH 06/12] test debug --- src/SidebarProvider.ts | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/SidebarProvider.ts b/src/SidebarProvider.ts index 824ab7d..bb61491 100644 --- a/src/SidebarProvider.ts +++ b/src/SidebarProvider.ts @@ -66,9 +66,7 @@ export class SidebarProvider implements vscode.WebviewViewProvider { vscode.commands.executeCommand('setContext', 'codesphere.isLoggedIn', true); cache.update("codesphere.isLoggedIn", true); webviewView.webview.html = this._getHtmlWebviewOverview(webviewView.webview); - cache.update('codesphere.workspaceOverview', cache.get('codesphere.currentWorkspace')); - vscode.commands.executeCommand('setContext', 'codesphere.workspaceOverview', cache.get('codesphere.currentWorkspace')); - console.log('Congratulations, your extension "codesphere" is now active! You are logged in.'); + // todo: instead of passing just the workspace id we need to pass the whole workspace object let currentWorkspace = parseInt(cache.get('codesphere.currentWorkspace') as string); const workspacesInTeam: any = cache.get("codesphere.workspaces"); @@ -86,7 +84,9 @@ export class SidebarProvider implements vscode.WebviewViewProvider { break; } } - cache.update("codesphere.currentconnectedWorkspace", matchingObject); + + console.log('matchingObject', matchingObject); + cache.update("codesphere.workspaceOverview", matchingObject); if (matchingObject) { this._view?.webview.postMessage({ @@ -96,6 +96,10 @@ export class SidebarProvider implements vscode.WebviewViewProvider { }, }); } + + // cache.update('codesphere.workspaceOverview', cache.get('codesphere.currentWorkspace')); + vscode.commands.executeCommand('setContext', 'codesphere.workspaceOverview', cache.get('codesphere.currentWorkspace')); + console.log('Congratulations, your extension "codesphere" is now active! You are logged in.'); } if (!cache.get("codesphere.isLoggedIn")) { From 987396c4e18aacf452c63d8336413c22a8fc2127 Mon Sep 17 00:00:00 2001 From: "jan-david.wiederstein" Date: Sun, 24 Nov 2024 14:08:20 +0100 Subject: [PATCH 07/12] fix initial state bugs --- src/SidebarProvider.ts | 2 +- src/extension.ts | 7 ++++++- webviews/components/Overview.svelte | 14 ++++++++------ 3 files changed, 15 insertions(+), 8 deletions(-) diff --git a/src/SidebarProvider.ts b/src/SidebarProvider.ts index bb61491..323bf80 100644 --- a/src/SidebarProvider.ts +++ b/src/SidebarProvider.ts @@ -55,7 +55,7 @@ export class SidebarProvider implements vscode.WebviewViewProvider { } - if (cache.get("codesphere.isLoggedIn") === true) { + if (cache.get("codesphere.isLoggedIn") === true && cache.get('codesphere.currentWorkspace') === '') { vscode.commands.executeCommand('setContext', 'codesphere.isLoggedIn', true); cache.update("codesphere.isLoggedIn", true); webviewView.webview.html = this._getHtmlForWebviewAfterSignIn(webviewView.webview); diff --git a/src/extension.ts b/src/extension.ts index 163d3b5..1cf654d 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -29,7 +29,12 @@ export function activate(context: vscode.ExtensionContext) { context.subscriptions.push( vscode.window.registerWebviewViewProvider( "codesphere-sidebar", - sidebarProvider + sidebarProvider, + { + webviewOptions: { + retainContextWhenHidden: true + } + } ) ); diff --git a/webviews/components/Overview.svelte b/webviews/components/Overview.svelte index ba1ad5d..ae60cf3 100644 --- a/webviews/components/Overview.svelte +++ b/webviews/components/Overview.svelte @@ -561,7 +561,7 @@ {/if} - {#if activeWorkspace === true && workspaceDeployed === true && connectedWorkspace === false} + {#if activeWorkspace === true && workspaceDeployed === true} -
-
- - + {#if connectedWorkspace === false} +
+
+ + +
-
+ {/if} {/if}
\ No newline at end of file From 0a03fb31fcf35e2cd68cf60a3d2b332cb85803a2 Mon Sep 17 00:00:00 2001 From: "jan-david.wiederstein" Date: Sun, 24 Nov 2024 14:48:09 +0100 Subject: [PATCH 08/12] remove scroll bar workspace overview --- webviews/components/Codesphere.svelte | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/webviews/components/Codesphere.svelte b/webviews/components/Codesphere.svelte index 7880c38..eee2cb9 100644 --- a/webviews/components/Codesphere.svelte +++ b/webviews/components/Codesphere.svelte @@ -213,17 +213,24 @@ color: white!important; } - .workspaceList { + .workspaceList { position: relative; - overflow-x: auto; + overflow-x: auto; white-space: nowrap; padding-left: 16px; } - .workspaceList:nth-child(2) { + .workspaceList:nth-child(1) { padding-top:8px } + + + .workspaceList { + scrollbar-width: none; + -ms-overflow-style: none; + } + .workspaceBox { display: flex; flex-direction: column; @@ -249,10 +256,12 @@ } .height-indicator { + margin: 0; + margin-bottom: -8px; position: absolute; left: 8px; height: 100%; - border-left: .5px solid #80808026;; + border-left: 1.8px solid #80808026; } From 3866700cbda786cc8c8b1bda2ff0c316dddcf068 Mon Sep 17 00:00:00 2001 From: Jan-David Wiederstein Date: Sun, 24 Nov 2024 15:31:00 +0100 Subject: [PATCH 09/12] add user avatar placeholder when not available --- webviews/components/Codesphere.svelte | 44 ++++++++++++++++++++++++++- 1 file changed, 43 insertions(+), 1 deletion(-) diff --git a/webviews/components/Codesphere.svelte b/webviews/components/Codesphere.svelte index eee2cb9..5c0932d 100644 --- a/webviews/components/Codesphere.svelte +++ b/webviews/components/Codesphere.svelte @@ -12,6 +12,7 @@ let notDeployedWorkspaces = []; let indexOfWorkspace; let currentWorkspace; + let username; function openOverview(workspaceId, teamId) { vscode.postMessage({ @@ -93,6 +94,10 @@ break; case 'getUserData': user = JSON.parse(message.value); + console.log("User: " , user); + if (user.avatarURL === null) { + username = getInitials(user); + } break; case 'activeWorkspaces': activeWorkspaces = message.value; @@ -148,6 +153,26 @@ const teamIndex = teamArray.findIndex(team => team.id === teamId); teamArray[teamIndex].open = !teamArray[teamIndex].open; } + + function getInitials(user) { + console.log("User Names: ", user.firstName, user.lastName, user.email); + + if (user.firstName && user.lastName) { + return user.firstName[0].toUpperCase() + user.lastName[0].toUpperCase(); + } + + if (user.firstName) { + return user.firstName[0].toUpperCase(); + } + + if (user.lastName) { + return user.lastName[0].toUpperCase(); + } + + if (user.email) { + return user.email[0].toUpperCase(); + } + } @@ -200,6 +225,17 @@ height: 32px; border-radius: 50%; object-fit: cover; + display: flex; + align-items: center; + justify-content: center; + font-size: 1rem; + font-weight: bold; + color: white; + text-transform: uppercase; + } + + .defaultAvatar { + background-color: #80808026; } .workspace { @@ -270,7 +306,13 @@

Your Teams

- User Avatar + {#if user.avatarURL} + User Avatar + {:else} +
+ {username} +
+ {/if}
{#each teamArray as team (team.id)} From c9927476a4427394d0363be510ae6e49fd2d1552 Mon Sep 17 00:00:00 2001 From: Jan-David Wiederstein Date: Tue, 3 Dec 2024 00:51:11 +0100 Subject: [PATCH 10/12] initial state MSD setup --- src/CiPipelineProvider.ts | 261 +++- src/SidebarProvider.ts | 3 +- src/ts/wsService.ts | 137 ++- webviews/components/MSDCiPipeline.svelte | 1387 ++++++++++++++++++++++ webviews/pages/msdcipipeline.ts | 7 + 5 files changed, 1711 insertions(+), 84 deletions(-) create mode 100644 webviews/components/MSDCiPipeline.svelte create mode 100644 webviews/pages/msdcipipeline.ts diff --git a/src/CiPipelineProvider.ts b/src/CiPipelineProvider.ts index c3eb537..b85c8ea 100644 --- a/src/CiPipelineProvider.ts +++ b/src/CiPipelineProvider.ts @@ -4,10 +4,14 @@ import * as wsLib from 'ws'; const { setupWs, request, getUaSocket, + getDsSocket, + getWsSocket, checkCiPipelineStructure, checkCiPipelineState, + checkMSDCiPipelineState, ciStepHandler, ciStageStatusHandler, + landscapeShape, } = require('./ts/wsService'); export class CiPipelineProvider implements vscode.WebviewViewProvider { @@ -39,13 +43,14 @@ export class CiPipelineProvider implements vscode.WebviewViewProvider { webviewView.webview.html = this._getHtmlWebview(webviewView.webview); - webviewView.webview.onDidReceiveMessage(async (data) => { - let socket: any; - let uaSocket = getUaSocket(); - let instanceURL: string = cache.get("codesphere.instanceURL") as string; - instanceURL = instanceURL.replace(/^https?:\/\//, ''); - instanceURL = instanceURL.replace(/\/$/, ''); + let socket: any; + let uaSocket = getUaSocket(); + let instanceURL: string = cache.get("codesphere.instanceURL") as string; + instanceURL = instanceURL.replace(/^https?:\/\//, ''); + instanceURL = instanceURL.replace(/\/$/, ''); + + webviewView.webview.onDidReceiveMessage(async (data) => { switch (data.type) { case "getCiPipelineStages": { const socketURL = `wss://${data.value.dataCenterId}.${instanceURL}/workspace-proxy`; @@ -59,17 +64,33 @@ export class CiPipelineProvider implements vscode.WebviewViewProvider { ciPipelineCheck.then((ci: any) => { ciStructure = ci; console.log("ciStructure: ", JSON.stringify(ciStructure)); - this._view?.webview.postMessage({ - type: "CIPipelineStages", - value: { - 'CIArray': `${JSON.stringify(ciStructure)}` + if (ciStructure.schemaVersion !== "v0.2") { + console.log("ciPipeline schema version is not v0.2"); + this._view?.webview.postMessage({ + type: "CIPipelineStages", + value: { + 'CIArray': `${JSON.stringify(ciStructure)}` + } + }); + } else { + console.log("ciPipeline schema version is v0.2"); + console.log(data.value.origin); + if (data.value.origin === "msd") { + + this._view?.webview.postMessage({ + type: "CIPipelineStages", + value: { + 'CIArray': `${JSON.stringify(ciStructure)}` + } + }); + } else { + webviewView.webview.html = this._getHtmlWebviewMSD(webviewView.webview); } - }); + } } ); await request(uaSocket, "pipelineStream", { workspaceId: workspaceID}, "workspace-proxy", 324); - break; } @@ -114,39 +135,121 @@ export class CiPipelineProvider implements vscode.WebviewViewProvider { const accessToken = cache.get("codesphere.accessTokenCache"); socket = await setupWs(new wsLib.WebSocket(socketURL), "workspace-proxy", accessToken, cache, workspaceId); let uaSocketconnect2 = getUaSocket(); + + const runStageServices = data.value.runStageServices; + + let prepare; let test; let run; - + const prepareCheck = checkCiPipelineState(uaSocketconnect2, 35); const testCheck = checkCiPipelineState(uaSocketconnect2, 136); - const runCheck = checkCiPipelineState(uaSocketconnect2, 237); - - testCheck.then((resultTest: any) => { - test = resultTest; - } - ); - - runCheck.then((resultRun: any) => { - run = resultRun; - }); - + prepareCheck.then((result: any) => { prepare = result; }); + + testCheck.then((resultTest: any) => { + test = resultTest; + } + ); - await request(uaSocketconnect2, "executionInfo", { workspaceId: workspaceId, stage: 'test' }, "workspace-proxy", 136); + if (data.value.origin === "msd") { + + const socketURLWs = `wss://${data.value.datacenterId}.${instanceURL}/workspace-service`; + socket = await setupWs(new wsLib.WebSocket(socketURLWs), "workspace-service", accessToken, cache, workspaceId); + let wsSocket = getWsSocket(); + let landscapeStructure; + + const landscapeShapeData = landscapeShape(wsSocket, 56); + + landscapeShapeData.then(async (result: any) => { + console.log("landscapeShape: ", result); + landscapeStructure = result.map(({ workspaceId, ...rest }) => rest); + console.log("landscapeShape: ", result); + + const waitForLandscapeUpdate = landscapeShape(wsSocket, 57); + + await request(wsSocket, "updateLandscape", { servers: landscapeStructure, workspaceId: workspaceId }, "workspace-service", 57); + + await waitForLandscapeUpdate.then(async (result: any) => { + console.log("waited for update: ", result); + }); + + }); + + await request(wsSocket, "landscapeStream", { workspaceId: workspaceId }, "landscapeStream", 56); + + delay(400); + + console.log("runStageServices: ", runStageServices); + let uaSocketDeploymentService = getDsSocket(); + + const MSDrunCheck = checkMSDCiPipelineState(uaSocketDeploymentService, 50, data.value.replicaCount, runStageServices); + + await request(uaSocketDeploymentService, "info", { workspaceId: workspaceId }, "info", 50); + + await MSDrunCheck.then(async (resultRun: any) => { + run = resultRun; + console.log("runstructure: ", run); + }); - await delay(200); - await request(uaSocketconnect2, "executionInfo", { workspaceId: workspaceId, stage: 'run' }, "workspace-proxy", 237); + let endpoint = 237; + let runCheck; + Object.entries(run).forEach(async ([serviceName, service]: [string, any]) => { + console.log("service: ", serviceName); + + const replicas = service.replicas; + for (const replicaKey of Object.keys(replicas)) { + console.log("Replica: ", replicaKey); + + runCheck = checkCiPipelineState(uaSocketconnect2, endpoint); + runCheck.then((resultRun: any) => { + console.log("resultRun: ", resultRun, "\nrun: ", run); + run[serviceName].replicas[replicaKey] = resultRun; + console.log("resultRun2: ", run); + + }); + + await request( + uaSocketconnect2, + "executionInfo", + { + replica: replicaKey, + server: serviceName, + stage: "run", + workspaceId: workspaceId, + }, + "workspace-proxy", + endpoint + ); + + endpoint++; + } + }); + + } else if (!data.value.origin) { + const runCheck = checkCiPipelineState(uaSocketconnect2, 237); + + runCheck.then((resultRun: any) => { + run = resultRun; + }); + + await request(uaSocketconnect2, "executionInfo", { workspaceId: workspaceId, stage: 'run' }, "workspace-proxy", 237); + await delay(200); + + } + + await request(uaSocketconnect2, "executionInfo", { workspaceId: workspaceId, stage: 'test' }, "workspace-proxy", 136); await delay(200); await request(uaSocketconnect2, "executionInfo", { workspaceId: workspaceId, stage: 'prepare' }, "workspace-proxy", 35); - await delay(200); + await delay(500); this._view?.webview.postMessage({ type: "ciPipelineStatus", @@ -177,6 +280,7 @@ export class CiPipelineProvider implements vscode.WebviewViewProvider { let uaSocket = getUaSocket(); let ciStageFinished = false; let ciPipelineStatus; + let runStageServices = data.value.msd; if (stage === "prepare") { endpoint = 36; @@ -188,36 +292,47 @@ export class CiPipelineProvider implements vscode.WebviewViewProvider { endpoint = 238; } - await request(uaSocket, "executionInfo", { workspaceId: workspaceId, stage: stage }, "workspace-proxy", endpoint); + if ((stage === "prepare" || stage === "test" || stage === "run") && !runStageServices) { + await request(uaSocket, "executionInfo", { workspaceId: workspaceId, stage: stage }, "workspace-proxy", endpoint); - const endpointArray = Array.from({length: data.value.stepcount}, (_, i) => 400 + i); - - await request(uaSocket, "startPipeline", { workspaceId: workspaceId, stage: stage }, "workspace-proxy", 32); - - for (let i = 0; i < data.value.stepcount; i++) { - request(uaSocket, "logs", { workspaceId: workspaceId, stage: stage, step:i }, "workspace-proxy", (400+i)); - } - - ciStageStatusHandler(uaSocket, endpoint, this.postMessageToWebview, stage).then((status: any) => { - ciPipelineStatus = status; - }); + const endpointArray = Array.from({length: data.value.stepcount}, (_, i) => 400 + i); + + await request(uaSocket, "startPipeline", { workspaceId: workspaceId, stage: stage }, "workspace-proxy", 32); + + for (let i = 0; i < data.value.stepcount; i++) { + request(uaSocket, "logs", { workspaceId: workspaceId, stage: stage, step:i }, "workspace-proxy", (400+i)); + } + + ciStageStatusHandler(uaSocket, endpoint, this.postMessageToWebview, stage).then((status: any) => { + ciPipelineStatus = status; + }); - let log = ciStepHandler(uaSocket, endpoint, endpointArray, this.postMessageToWebview, stage); + let log = ciStepHandler(uaSocket, endpoint, endpointArray, this.postMessageToWebview, stage); - - while (!ciStageFinished) { - await delay(1000); + + while (!ciStageFinished) { + await delay(1000); + + if (ciPipelineStatus === 'success') { + ciStageFinished = true; + } + + if (ciPipelineStatus === 'failure') { + ciStageFinished = true; + } + } + + break; + } else if (stage === "run") { + // TODO: open executionInfo for each service + // TODO: start run stage Pipeline + // TODO: open logs for each service + // stream logs for each service and replica + // stream status for each service and replica - if (ciPipelineStatus === 'success') { - ciStageFinished = true; - } - if (ciPipelineStatus === 'failure') { - ciStageFinished = true; - } } - - break; + } }; } @@ -276,4 +391,46 @@ export class CiPipelineProvider implements vscode.WebviewViewProvider { `; } + + private _getHtmlWebviewMSD(webview: vscode.Webview) { + const styleResetUri = webview.asWebviewUri( + vscode.Uri.joinPath(this._extensionUri, "media", "reset.css") + ); + const scriptUri = webview.asWebviewUri( + vscode.Uri.joinPath(this._extensionUri, "out", "compiled/msdcipipeline.js") + ); + const styleMainUri = webview.asWebviewUri( + vscode.Uri.joinPath(this._extensionUri, "out", "compiled/msdcipipeline.css") + ); + const styleVSCodeUri = webview.asWebviewUri( + vscode.Uri.joinPath(this._extensionUri, "media", "vscode.css") + ); + + // Use a nonce to only allow a specific script to be run. + const nonce = getNonce(); + + return ` + + + + + + + + + + + + + + + `; + } } \ No newline at end of file diff --git a/src/SidebarProvider.ts b/src/SidebarProvider.ts index 323bf80..a4f96b9 100644 --- a/src/SidebarProvider.ts +++ b/src/SidebarProvider.ts @@ -7,6 +7,7 @@ const { setupWs, request, waitForWorkspaceRunning, getUaSocket, + getDsSocket, giveWorkspaceName, afterTunnelInit, tunnelIsReady, @@ -686,7 +687,7 @@ export class SidebarProvider implements vscode.WebviewViewProvider { const socketURL = `wss://${data.value.datacenterId}.${instanceURL}/deployment-service`; const accessToken = await this.extensionContext.secrets.get("codesphere.accessToken") as string; socket = await setupWs(new wsLib.WebSocket(socketURL), "deployment-service", accessToken, cache, workspaceId); - uaSocket2 = getUaSocket(); + uaSocket2 = getDsSocket(); const codePromise = wakeUpWorkspace(uaSocket2); diff --git a/src/ts/wsService.ts b/src/ts/wsService.ts index d80c95a..41e5128 100644 --- a/src/ts/wsService.ts +++ b/src/ts/wsService.ts @@ -6,6 +6,7 @@ const fs = require('fs'); let wsSeq = 1; let dsSocket: any; let uaSocket: any; +let wsSocket: any; const setupWs = (ws: any, name: string, accessToken: undefined, cache?:any, workspaceID?: string) => { @@ -39,39 +40,19 @@ const setupWs = (ws: any, name: string, accessToken: undefined, cache?:any, work if (name === "workspace-proxy") { uaSocket = ws; - } - - ws.send(JSON.stringify({ - method: "setClientContext", - endpointId: 1, - args: { - requestHeaders: { - Authorization: `Bearer ${accessToken}` - }, - responseHeaders: {}, - httpStatusCode: 200 - } - })); - + } + if (name === "deployment-service") { - uaSocket = ws; - } - - ws.send(JSON.stringify({ - method: "setClientContext", - endpointId: 1, - args: { - requestHeaders: { - Authorization: `Bearer ${accessToken}` - }, - responseHeaders: {}, - httpStatusCode: 200 - } - })); + dsSocket = ws; + } if (name === "ide-service") { uaSocket = ws; - } + } + + if (name === "workspace-service") { + wsSocket = ws; + } ws.send(JSON.stringify({ method: "setClientContext", @@ -84,7 +65,6 @@ const setupWs = (ws: any, name: string, accessToken: undefined, cache?:any, work httpStatusCode: 200 } })); - }); ws.on("message", (data: { toString: () => any; }) => { @@ -404,6 +384,71 @@ const checkCiPipelineState = async (deploymentSocket: any, endpointId: number) = }); }; +const checkMSDCiPipelineState = async (deploymentSocket: any, endpointId: number, replicaCount: number, runStageServices: any) => { + return new Promise((resolve, reject) => { + + let count = 0; + + // all replicas are placed into the empty Array + Object.values(runStageServices).forEach((service: any) => { + service.replicas = []; + }); + const nexLogHandler = (msg: any) => { + try { + let msgTest = msg.toString(); + let parsedMsg = JSON.parse(msgTest); + + if (msgTest.includes(`"endpointId":${endpointId}`)) { + if (parsedMsg.reply.server !== "codesphere-ide") { + const serverName = parsedMsg.reply.server; + const hostname = parsedMsg.reply.hostname; + + if (runStageServices[serverName]) { + if (typeof runStageServices[serverName].replicas !== "object" || Array.isArray(runStageServices[serverName].replicas)) { + runStageServices[serverName].replicas = {}; + } + + if (!runStageServices[serverName].replicas[hostname]) { + runStageServices[serverName].replicas[hostname] = {}; + } + + console.log( + `Hostname ${hostname} wurde zu ${serverName}.replicas hinzugefügt.` + ); + count++; + } else { + console.log(`Server ${serverName} existiert nicht in runStageServices.`); + } + } + } + + if (parsedMsg.reply.server === "codesphere-ide") { + count++; + } + + if (count === replicaCount) { + deploymentSocket.off("message", nexLogHandler); + deploymentSocket.off("error", errorHandler); + console.log("Alle Hostnamen wurden erfolgreich in runStageServices eingetragen."); + console.log("gtt ", runStageServices); + resolve(runStageServices); + } + } catch (error) { + console.error("Error parsing message:", error); + reject(error); + } + }; + + const errorHandler = (err: any) => { + console.log("Socket exited with error:" + err); + reject(err); + }; + + deploymentSocket.on("message", nexLogHandler); + deploymentSocket.on("error", errorHandler); + }); +}; + const getRemoteURL = async (deploymentSocket: any) => { return new Promise((resolve, reject) => { const nexLogHandler = (msg: any) => { @@ -664,6 +709,33 @@ const getSubdomainStructure = async (deploymentSocket: any, endpointId: any) => }; +const landscapeShape = async (deploymentSocket: any, endpointId: any) => { + return new Promise((resolve, reject) => { + const nexLogHandler = (msg: any) => { + try { + let msgTest = msg.toString(); + let parsedMsg = JSON.parse(msgTest); + if (msgTest.includes(`"endpointId":${endpointId}`)) { + deploymentSocket.off("message", nexLogHandler); + resolve(parsedMsg.reply); + } + + } catch (error) { + console.error("Error parsing message:", error); + reject(error); + } + }; + + const errorHandler = (err: any) => { + console.log("Socket exited with error:" + err); + reject(err); + }; + + deploymentSocket.on("message", nexLogHandler); + deploymentSocket.on("error", errorHandler); + }); +}; + module.exports = { @@ -681,6 +753,7 @@ module.exports = { waitForTerminal, waitForCiPipeline, checkCiPipelineState, + checkMSDCiPipelineState, getRemoteURL, getGitHubToken, isVSIX, @@ -688,6 +761,8 @@ module.exports = { ciStepHandler, ciStageStatusHandler, getSubdomainStructure, + landscapeShape, getUaSocket: () => uaSocket, - getDsSocket: () => dsSocket + getDsSocket: () => dsSocket, + getWsSocket: () => wsSocket }; \ No newline at end of file diff --git a/webviews/components/MSDCiPipeline.svelte b/webviews/components/MSDCiPipeline.svelte new file mode 100644 index 0000000..abbafd2 --- /dev/null +++ b/webviews/components/MSDCiPipeline.svelte @@ -0,0 +1,1387 @@ + + + + +
+ + + + + + +

CI Pipeline

+
+ +
+
+

+ Stages +

+
{ toggleActive("prepare") }} role="presentation" style="background-color: {prepareStageSuccess === 'success' ? 'rgba(0, 128, 0, 0.6)' : (prepareStageSuccess === 'failure' || prepareStageSuccess === 'aborted' ? 'rgba(255, 0, 0, 0.6)' : 'inherit')}"> +
+ + + + + +
Prepare
+
+ {#key prepareStageSate} + {#if prepareStageSate} +
+
+
+
+
+
+ {#if animateCircles} + + {/if} + {/if} + {/key} +
+
{ toggleActive("test") }} role="presentation" style="background-color: {testStageSuccess === 'success' ? 'rgba(0, 128, 0, 0.6)' : (testStageSuccess === 'failure' || testStageSuccess === 'aborted' ? 'rgba(255, 0, 0, 0.6)' : 'inherit')}"> +
+ + + + + + +
Test
+
+ {#key testStageSate} + {#if testStageSate} +
+
+
+
+
+
+ {#if animateCircles} + + {/if} + {/if} + {/key} +
+
{ toggleActive("run") }} role="presentation" style="background-color: {runStageSuccess === 'success' ? 'rgba(0, 128, 0, 0.6)' : (runStageSuccess === 'failure' || runStageSuccess === 'aborted' ? 'rgba(255, 0, 0, 0.6)' : 'inherit')}"> +
+ + + +
Run
+
+ {#key runStageSate} + {#if runStageSate} +
+
+
+
+
+
+ {#if animateCircles} + + {/if} + {/if} + {/key} +
+
+ + + +
+
+

{selectedStage.charAt(0).toUpperCase() + selectedStage.slice(1)}

+ + +
+ {#if selectedStage == "run"} + Note: This stage restarts if any step fails. + {/if} +
+
+
+ {#if showPrepare} + {#each prepareStageSteps as step, index} +
toggleAccordion(index, 'prepare')} role="presentation"> + +
+ + {#if step.open} + + + + {:else} + + + + {/if} + {#if step.state === 'success'} + + + + {:else if step.state == 'failure' || step.state == 'aborted'} + + + + + + {:else if step.state == 'running'} +
+
+
+
+
+
+ {#if animateCircles} + + {/if} + {:else if step.state == 'waiting'} + + + + + + {/if} + {#if step.name == undefined} + {step.command} + {:else} + {step.name} + {/if} +
+
+
+ {#if step.log} +
+                                    {@html step.log}
+                                
+ {/if} +
+
+
+ {/each} + {/if} + + {#if showTest } + {#each testStageSteps as step, index} +
toggleAccordion(index, 'test')} role="presentation"> + +
+ + {#if step.open} + + + + {:else} + + + + {/if} + {#if step.state === 'success'} + + + + {:else if step.state == 'failure'} + + + + + + {:else if step.state == 'running'} +
+
+
+
+
+
+ {#if animateCircles} + + {/if} + {:else if step.state == 'waiting'} + + + + + + {/if} + {#if step.name == undefined} + {step.command} + {:else} + {step.name} + {/if} +
+
+
+ {#if step.log} +
+                                    {@html step.log}
+                                
+ {/if} +
+
+
+ {/each} + {/if} + + {#if showRun } +
+ +
+ {#each Object.entries(runStageServices) as service, index} + + +
toggleServices(service[0])} role="presentation"> + + {#if !Object.values(service[1].replicas).some(replica => replica.state === 'aborted' || replica.state === 'failure')} + {#if Object.values(service[1].replicas).every(replica => replica.state === "success")} + + + + {:else if Object.values(service[1].replicas).every(replica => replica.state === 'waiting')} + + + + + + {:else} +
+
+
+
+
+
+ {#if animateCircles} + + {/if} + {/if} + {:else} + + + + + {/if} + {service[0]} +
+ {/each} +
+ +
+ {#each Object.entries(runStageServices) as service, index} + {#if service[0] === showService} + {#each service[1].steps as step, indexStep} +
+
toggleAccordion(indexStep, 'run', service = service[0])} role="presentation"> + + + + {#if step.open} + + + + {:else} + + + + {/if} + {#if Object.values(service[1].replicas).every(replica => + replica.steps[indexStep].state === 'success') + } + + + + {:else if Object.values(service[1].replicas).some(replica => + replica.steps[indexStep].state === 'failure' || + replica.steps[indexStep].state === 'aborted') + } + + + + + {:else if Object.values(service[1].replicas).some(replica => + replica.steps[indexStep].state === 'running') + } +
+
+
+
+
+
+ {#if animateCircles} + + {/if} + {:else if Object.values(service[1].replicas).some(replica => + replica.steps[indexStep].state === 'waiting') + } + + + + + + {/if} + {#if step.name == undefined} + {step.command} + {:else} + {step.name} + {/if} +
+
+
+ {#each Object.entries(service[1].replicas) as replica, index} + + + +
toggleReplica(service[0], replica[0])} role="presentation"> + {#if replica[1].steps[indexStep].state === "running"} +
+
+
+
+
+
+ {#if animateCircles} + + {/if} + {:else if replica[1].steps[indexStep].state === "success"} + + + + {:else if replica[1].steps[indexStep].state === "failure" || replica[1].steps[indexStep].state === "aborted"} + + + + + {:else if replica[1].steps[indexStep].state === "waiting"} + + + + + + {/if} + {replica[0].slice(-5)} +
+ {/each} +
+
+ + + + +
+
+ +
+ {/each} + {/if} + {/each} +
+
+ {/if} +
+
+
\ No newline at end of file diff --git a/webviews/pages/msdcipipeline.ts b/webviews/pages/msdcipipeline.ts new file mode 100644 index 0000000..391fbca --- /dev/null +++ b/webviews/pages/msdcipipeline.ts @@ -0,0 +1,7 @@ +import App from "../components/MSDCiPipeline.svelte"; + +const app = new App({ + target: document.body, +}); + +export default app; \ No newline at end of file From 3c5dcef9fe6ff7e3c90becc68b91e46313f95c1e Mon Sep 17 00:00:00 2001 From: Jan-David Wiederstein Date: Tue, 3 Dec 2024 18:12:20 +0100 Subject: [PATCH 11/12] state stream & log stream MSD --- src/CiPipelineProvider.ts | 118 ++++++++++++++++-- src/ts/wsService.ts | 107 ++++++++++++++++ webviews/components/MSDCiPipeline.svelte | 150 ++++++++++++++++++++--- 3 files changed, 351 insertions(+), 24 deletions(-) diff --git a/src/CiPipelineProvider.ts b/src/CiPipelineProvider.ts index b85c8ea..4a71d30 100644 --- a/src/CiPipelineProvider.ts +++ b/src/CiPipelineProvider.ts @@ -10,7 +10,9 @@ const { setupWs, checkCiPipelineState, checkMSDCiPipelineState, ciStepHandler, + ciStepHandlerMSD, ciStageStatusHandler, + ciStageStatusHandlerMSD, landscapeShape, } = require('./ts/wsService'); @@ -172,7 +174,10 @@ export class CiPipelineProvider implements vscode.WebviewViewProvider { console.log("landscapeShape: ", result); const waitForLandscapeUpdate = landscapeShape(wsSocket, 57); - + // TODO: instead of just updating the Landscape this should check wether its synced or not + // if not synced a message needs to be sent to the webview, so the snyc button can be showed to the user (and break this code block) + // after the user clicks on the sync button the landscape should be updated with this case, but we need an additional key-value pair + // which indicates, that the user wants to update the landscape await request(wsSocket, "updateLandscape", { servers: landscapeStructure, workspaceId: workspaceId }, "workspace-service", 57); await waitForLandscapeUpdate.then(async (result: any) => { @@ -273,7 +278,7 @@ export class CiPipelineProvider implements vscode.WebviewViewProvider { const delay = (ms: any) => new Promise(resolve => setTimeout(resolve, ms)); const workspaceId = parseInt(data.value.workspaceId); const stage = data.value.stage; - let endpoint: number = 0; + let endpoint: number = 777; const socketURL = `wss://${data.value.dataCenterId}.${instanceURL}/workspace-proxy`; const accessToken = await this.extensionContext.secrets.get("codesphere.accessToken") as string; socket = await setupWs(new wsLib.WebSocket(socketURL), "workspace-proxy", accessToken, cache, workspaceId); @@ -323,12 +328,109 @@ export class CiPipelineProvider implements vscode.WebviewViewProvider { } break; - } else if (stage === "run") { - // TODO: open executionInfo for each service - // TODO: start run stage Pipeline - // TODO: open logs for each service - // stream logs for each service and replica - // stream status for each service and replica + } else if (stage === "run" && runStageServices) { + const generateReplicaEndpoints = (services: any) => { + let endpointCounter = 401; + const replicaEndpoints: Record = {}; + + for (const serviceKey in services) { + const service = services[serviceKey]; + if (service.replicas) { + for (const replicaKey in service.replicas) { + replicaEndpoints[replicaKey] = endpointCounter++; + } + } + } + + return replicaEndpoints; + }; + + const endpointArrayReplica = generateReplicaEndpoints(runStageServices); + console.log("endpointArrayReplica: ", endpointArrayReplica); + + let status = ciStageStatusHandlerMSD(uaSocket, endpointArrayReplica, this.postMessageToWebview, stage); + + Object.entries(runStageServices).forEach(async ([serviceName, service]: [string, any]) => { + console.log("service: ", serviceName); + + const replicas = service.replicas; + + for (const replicaKey of Object.keys(replicas)) { + console.log("Replica: ", replicaKey); + + let endpointReplica = endpointArrayReplica[replicaKey]; + + console.log("endpointReplica: ", endpointReplica); + + await request( + uaSocket, + "executionInfo", + { + replica: replicaKey, + server: serviceName, + stage: "run", + workspaceId: workspaceId, + }, + "workspace-proxy", + endpointReplica + ); + } + } + ); + + await request(uaSocket, "startPipeline", { workspaceId: workspaceId, stage: stage }, "workspace-proxy", 32); + + const generateReplicaStepEndpoints = (services: any) => { + let endpointCounter = 501; + const replicaStepEndpoints: Record }> = {}; + + for (const serviceKey in services) { + const service = services[serviceKey]; + if (service.replicas) { + for (const replicaKey in service.replicas) { + const replica = service.replicas[replicaKey]; + + // Initialisiere die steps für das aktuelle Replica + replicaStepEndpoints[replicaKey] = { steps: {} }; + + // Gehe alle Steps des Replicas durch und weise jedem Step eine eigene EndpointId zu + replica.steps.forEach((step: any, index: number) => { + replicaStepEndpoints[replicaKey].steps[index] = endpointCounter++; + }); + } + } + } + + return replicaStepEndpoints; + }; + + const replicaStepEndpoints = generateReplicaStepEndpoints(runStageServices); + + console.log("Replica Step Endpoints", replicaStepEndpoints); + + let log = ciStepHandlerMSD(uaSocket, replicaStepEndpoints, this.postMessageToWebview, stage); + Object.entries(runStageServices).forEach(async ([serviceName, service]: [string, any]) => { + console.log("service: ", serviceName); + + const replicas = service.replicas; + for (const replicaKey of Object.keys(replicas)) { + console.log("Replica: ", replicaKey); + + const steps = replicas[replicaKey].steps; + for (const stepIndex in steps) { + const endpointId = replicaStepEndpoints[replicaKey].steps[stepIndex]; + console.log(`Sending request for step ${stepIndex} with endpointId ${endpointId}`); + + request( + uaSocket, + "logs", + { workspaceId: workspaceId, stage: stage, step: parseInt(stepIndex), replica: replicaKey, server: serviceName }, + "workspace-proxy", + endpointId + ); + } + } + }); } diff --git a/src/ts/wsService.ts b/src/ts/wsService.ts index 41e5128..6c39c5b 100644 --- a/src/ts/wsService.ts +++ b/src/ts/wsService.ts @@ -613,6 +613,59 @@ const ciStepHandler = async (deploymentSocket: any, endpointId: number, endpoint }); }; +const ciStepHandlerMSD = async (deploymentSocket: any, replicaStepEndpoints: Record }>, postMessage: Function, stage: string) => { + return new Promise((resolve, reject) => { + const ciStageStatus = (msg: any) => { + try { + let msgTest = msg.toString(); + let parsedMsg = JSON.parse(msgTest); + + // Iteriere über alle ReplicaKeys + for (const replicaKey in replicaStepEndpoints) { + const steps = replicaStepEndpoints[replicaKey].steps; + + // Überprüfe alle Schritte für das aktuelle Replica + for (const stepIndex in steps) { + const endpointId = steps[stepIndex]; // EndpointId für diesen Step und Replica + + // Überprüfe, ob die msg die richtige endpointId enthält + if (msgTest.includes(`"endpointId":${endpointId}`)) { + if (parsedMsg.reply) { + console.log("parsedMsg LOGS: ", parsedMsg.reply[0].data, "\nreplicaKey", replicaKey, "\nstepIndex", stepIndex, "\nendpointId", endpointId); + + postMessage('updateCiPipelineLogsMSD', + { + log: parsedMsg.reply[0].data, + stepIndex: stepIndex, + replicaKey: replicaKey + } + ); + } + } + } + } + } catch (error) { + console.error("Error parsing message:", error); + reject(error); + } + }; + + const errorHandler = (err: any) => { + console.log("Socket exited with error:" + err); + reject(err); + }; + + + deploymentSocket.on("message", ciStageStatus); + deploymentSocket.on("error", errorHandler); + + const cleanup = () => { + deploymentSocket.off("message", ciStageStatus); + deploymentSocket.off("error", errorHandler); + }; + }); +}; + const ciStageStatusHandler = async (deploymentSocket: any, endpointId: number, postMessage: Function, stage: string) => { return new Promise((resolve, reject) => { const ciStageStatus = (msg: any) => { @@ -680,6 +733,58 @@ const ciStageStatusHandler = async (deploymentSocket: any, endpointId: number, p }); }; +const ciStageStatusHandlerMSD = async (deploymentSocket: any, endpointArrayReplica: Record, postMessage: Function, stage: string) => { + return new Promise((resolve, reject) => { + const ciStageStatus = (msg: any) => { + try { + let msgTest = msg.toString(); + let parsedMsg = JSON.parse(msgTest); + console.log('loooool', parsedMsg); + console.log('loooool', endpointArrayReplica); + + if (Object.values(endpointArrayReplica).includes(parsedMsg.endpointId)) { + // Zugehörigen replicaKey finden + const replicaKey = Object.entries(endpointArrayReplica).find( + ([key, value]) => value === parsedMsg.endpointId + )?.[0]; // Der Key wird hier extrahiert + + if (parsedMsg.reply) { + console.log( + 'parsedMsg.reply: ', + parsedMsg.reply, + '\nreplicaKey: ', + replicaKey, + '\nendpointId', + parsedMsg.endpointId // Ausgabe des replicaKeys + ); + //TODO: use postMessage to send data to webview, so that the run Stage status can be updated + postMessage('updateCiStageStatusMSD', + { + stateReplica: parsedMsg.reply.state, + stepsReplica: parsedMsg.reply.steps, + replicaKey: replicaKey + } + ); + } + } + } catch (error) { + console.error("Error parsing message:", error); + reject(error); + } + }; + + const errorHandler = (err: any) => { + console.log("Socket exited with error:" + err); + reject(err); + }; + + + deploymentSocket.on("message", ciStageStatus); + deploymentSocket.on("error", errorHandler); + }); +}; + + const getSubdomainStructure = async (deploymentSocket: any, endpointId: any) => { return new Promise((resolve, reject) => { const nexLogHandler = (msg: any) => { @@ -759,7 +864,9 @@ module.exports = { isVSIX, checkCiPipelineStructure, ciStepHandler, + ciStepHandlerMSD, ciStageStatusHandler, + ciStageStatusHandlerMSD, getSubdomainStructure, landscapeShape, getUaSocket: () => uaSocket, diff --git a/webviews/components/MSDCiPipeline.svelte b/webviews/components/MSDCiPipeline.svelte index abbafd2..c220771 100644 --- a/webviews/components/MSDCiPipeline.svelte +++ b/webviews/components/MSDCiPipeline.svelte @@ -1,3 +1,16 @@ + + + + + + + + + + + + +