diff --git a/src/CiPipelineProvider.ts b/src/CiPipelineProvider.ts index c3eb537..80f373c 100644 --- a/src/CiPipelineProvider.ts +++ b/src/CiPipelineProvider.ts @@ -4,10 +4,16 @@ import * as wsLib from 'ws'; const { setupWs, request, getUaSocket, + getDsSocket, + getWsSocket, checkCiPipelineStructure, checkCiPipelineState, + checkMSDCiPipelineState, ciStepHandler, + ciStepHandlerMSD, ciStageStatusHandler, + ciStageStatusHandlerMSD, + landscapeShape, } = require('./ts/wsService'); export class CiPipelineProvider implements vscode.WebviewViewProvider { @@ -39,13 +45,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 +66,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 +137,124 @@ 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); + // 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) => { + 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", @@ -170,13 +278,14 @@ 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); let uaSocket = getUaSocket(); let ciStageFinished = false; let ciPipelineStatus; + let runStageServices = data.value.msd; if (stage === "prepare") { endpoint = 36; @@ -188,36 +297,144 @@ 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); + 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); + + + while (!ciStageFinished) { + await delay(1000); + + if (ciPipelineStatus === 'success') { + ciStageFinished = true; + } + + if (ciPipelineStatus === 'failure') { + ciStageFinished = true; + } + } + + break; + } else if (stage === "run" && runStageServices) { + const generateReplicaEndpoints = (services: any) => { + let endpointCounter = 401; + const replicaEndpoints: Record = {}; - for (let i = 0; i < data.value.stepcount; i++) { - request(uaSocket, "logs", { workspaceId: workspaceId, stage: stage, step:i }, "workspace-proxy", (400+i)); - } + for (const serviceKey in services) { + const service = services[serviceKey]; + if (service.replicas) { + for (const replicaKey in service.replicas) { + replicaEndpoints[replicaKey] = endpointCounter++; + } + } + } - ciStageStatusHandler(uaSocket, endpoint, this.postMessageToWebview, stage).then((status: any) => { - ciPipelineStatus = status; - }); + return replicaEndpoints; + }; + + const endpointArrayReplica = generateReplicaEndpoints(runStageServices); + console.log("endpointArrayReplica: ", endpointArrayReplica); - let log = ciStepHandler(uaSocket, endpoint, endpointArray, this.postMessageToWebview, stage); + let status = ciStageStatusHandlerMSD(uaSocket, endpointArrayReplica, this.postMessageToWebview, stage); - - while (!ciStageFinished) { - await delay(1000); + 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); - if (ciPipelineStatus === 'success') { - ciStageFinished = true; + 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); - if (ciPipelineStatus === 'failure') { - ciStageFinished = true; + 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++; + }); + } + } } - } - break; + 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 + ); + } + } + }); + + + } + } }; } @@ -276,4 +493,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..33618d7 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; }) => { @@ -94,7 +74,7 @@ const setupWs = (ws: any, name: string, accessToken: undefined, cache?:any, work }); - + ws.setMaxListeners(200); return new Promise(resolve => { ws.on('open', () => resolve(ws)); @@ -382,7 +362,8 @@ const checkCiPipelineState = async (deploymentSocket: any, endpointId: number) = try { let msgTest = msg.toString(); let parsedMsg = JSON.parse(msgTest); - + + console.log("runstage state: ", parsedMsg); if (msgTest.includes(`"endpointId":${endpointId}`)) { deploymentSocket.off("message", nexLogHandler); deploymentSocket.off("error", errorHandler); @@ -404,6 +385,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) => { @@ -568,6 +614,60 @@ 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); + + console.log("CiStepHandlerMSD: ", parsedMsg); + // 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) => { @@ -615,7 +715,7 @@ const ciStageStatusHandler = async (deploymentSocket: any, endpointId: number, p if (parsedMsg.reply.state === "running") { postMessage('updateCiStageStatus', {status: parsedMsg.reply.steps, stage: stage, state: parsedMsg.reply.state} ); - + // FIXME } } } catch (error) { @@ -635,6 +735,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) => { @@ -664,6 +816,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,13 +860,18 @@ module.exports = { waitForTerminal, waitForCiPipeline, checkCiPipelineState, + checkMSDCiPipelineState, getRemoteURL, getGitHubToken, isVSIX, checkCiPipelineStructure, ciStepHandler, + ciStepHandlerMSD, ciStageStatusHandler, + ciStageStatusHandlerMSD, getSubdomainStructure, + landscapeShape, getUaSocket: () => uaSocket, - getDsSocket: () => dsSocket + getDsSocket: () => dsSocket, + getWsSocket: () => wsSocket }; \ No newline at end of file diff --git a/webviews/components/CiPipeline.svelte b/webviews/components/CiPipeline.svelte index 5f9034c..36d7fe2 100644 --- a/webviews/components/CiPipeline.svelte +++ b/webviews/components/CiPipeline.svelte @@ -451,7 +451,19 @@ .circle-container { position: relative; width: 20px; - height: 20px; + height: 20px; + display: flex; + justify-content: center; + align-items: center; + } + + .circle-container-stages { + position: relative; + width: 40px; + height: 40px; + display: flex; + justify-content: center; + align-items: center; } .inner-circle { @@ -518,6 +530,26 @@ } } + .loader { + width: 70%; + height: 70%; + border: 2px solid rgba(128, 128, 128, 0.2); + border-bottom-color: #814BF6; + border-radius: 50%; + display: inline-block; + box-sizing: border-box; + animation: rotation 2s linear infinite; + } + + @keyframes rotation { + 0% { + transform: rotate(0deg); + } + 100% { + transform: rotate(360deg); + } + } + .animation-container { display: flex; flex-direction: row; @@ -549,7 +581,7 @@ flex-direction: column; justify-content: space-between; margin-top: 10px; - width: 33%; + width: 300px; } .pipelineStageTitle { @@ -623,6 +655,7 @@ display: flex; gap: 12px; align-items: center; + height: 40px; } .ci-pipeline-container { @@ -794,9 +827,8 @@ {#key prepareStageSate} {#if prepareStageSate}
-
-
-
+
+
{#if animateCircles} @@ -820,9 +852,8 @@ {#key testStageSate} {#if testStageSate}
-
-
-
+
+
{#if animateCircles} @@ -843,9 +874,8 @@ {#key runStageSate} {#if runStageSate}
-
-
-
+
+
{#if animateCircles} @@ -898,8 +928,7 @@ {:else if step.state == 'running'}
-
-
+
{#if animateCircles} @@ -961,8 +990,7 @@ {:else if step.state == 'running'}
-
-
+
{#if animateCircles} @@ -1024,8 +1052,7 @@ {:else if step.state == 'running'}
-
-
+
{#if animateCircles} diff --git a/webviews/components/MSDCiPipeline.svelte b/webviews/components/MSDCiPipeline.svelte new file mode 100644 index 0000000..4ceb63a --- /dev/null +++ b/webviews/components/MSDCiPipeline.svelte @@ -0,0 +1,1556 @@ + + + + + + + + + + + + + + + + + +
+ + + + + + +

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 Object.entries(service[1].replicas) as replica, index} + {#if replica[1].show} + {#if replica[1].steps[indexStep].log} +
+                                                                    {@html replica[1].steps[indexStep].log}
+                                                                
+ {/if} + {/if} + {/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