Repository: bitswan-space/bitswan-editor
Pull request #39: feat: add logic to render a button that can open a url of an automation
Author: login: JachymDolezal
Created: 2025-04-11T11:21:40Z | Merged: 2025-04-28T10:37:21Z



Diff:
diff --git a/Extension/package.json b/Extension/package.json
index 40bc5de..bbf1bf1 100644
--- a/Extension/package.json
+++ b/Extension/package.json
@@ -177,6 +177,11 @@
         "title": "Restart Automation",
         "icon": "$(debug-restart)"
       },
+      {
+        "command": "bitswan.openExternalUrl",
+        "title": "Open External URL",
+        "icon": "$(link-external)"
+      },
       {
         "command": "bitswan.showAutomationLogs",
         "title": "Show Logs",
@@ -232,12 +237,17 @@
         },
         {
           "command": "bitswan.stopAutomation",
-          "when": "view == bitswan-automations && viewItem =~ /^automation,active,running$/",
+          "when": "view == bitswan-automations && viewItem =~ /^automation,active,running( |$)/",
+          "group": "inline@1"
+        },
+        {
+          "command": "bitswan.openExternalUrl",
+          "when": "view == bitswan-automations && viewItem =~ /^automation,active,running( |$).*urlStatus:url/",
           "group": "inline@1"
         },
         {
           "command": "bitswan.startAutomation",
-          "when": "view == bitswan-automations && viewItem =~ /^automation,active,(paused|exited)$/",
+          "when": "view == bitswan-automations && viewItem =~ /^automation,active,(paused|exited)( |$)/",
           "group": "inline@1"
         },
         {
diff --git a/Extension/src/extension.ts b/Extension/src/extension.ts
index f16878c..482442f 100644
--- a/Extension/src/extension.ts
+++ b/Extension/src/extension.ts
@@ -117,6 +117,18 @@ export function activate(context: vscode.ExtensionContext) {
     let refreshImagesCommand = vscode.commands.registerCommand('bitswan.refreshImages', 
         async () => imageCommands.refreshImagesCommand(context, imagesProvider));
  
+    let openExternalUrlCommand = vscode.commands.registerCommand(
+        "bitswan.openExternalUrl",
+        async (item: AutomationItem) => {
+            const url = item.automationUrl;
+            try {
+            await vscode.env.openExternal(vscode.Uri.parse(url));
+            vscode.window.showInformationMessage(`Opened ${item.name} in browser`);
+            } catch (err) {
+            vscode.window.showErrorMessage(`Failed to open URL: ${url}`);
+            }
+        },
+        );
     
     let startAutomationCommand = vscode.commands.registerCommand('bitswan.startAutomation', 
         async (item: AutomationItem) => itemCommands.makeItemCommand({
@@ -223,6 +235,7 @@ export function activate(context: vscode.ExtensionContext) {
     context.subscriptions.push(activateGitOpsCommand);
     context.subscriptions.push(refreshAutomationsCommand);
     context.subscriptions.push(refreshImagesCommand);
+    context.subscriptions.push(openExternalUrlCommand);
     context.subscriptions.push(restartAutomationCommand);
     context.subscriptions.push(startAutomationCommand);
     context.subscriptions.push(stopAutomationCommand);
@@ -288,4 +301,4 @@ export function deactivate() {
     // Dispose the main output channel
     outputChannel.appendLine('BitSwan Extension Deactivated');
     outputChannel.dispose();
-}
+}
\ No newline at end of file
diff --git a/Extension/src/lib.ts b/Extension/src/lib.ts
index e6cee44..b07652a 100644
--- a/Extension/src/lib.ts
+++ b/Extension/src/lib.ts
@@ -61,20 +61,33 @@ export const activateDeployment = async (deployUrl: string, secret: string) => {
   return response.status == 200;
 }
 
-export const getAutomations = async (automationsUrl: string, secret: string) => {
-  const response = await axios.get(
-    automationsUrl, {
+export const getAutomations = async (
+  automationsUrl: string,
+  secret: string,
+) => {
+  const response = await axios.get(automationsUrl, {
     headers: {
-      'Authorization': `Bearer ${secret}`,
+      Authorization: `Bearer ${secret}`,
     },
   });
-
+  
   if (response.status == 200) {
-    return response.data;
+
+    if (!Array.isArray(response.data)) {
+      console.warn("[getAutomations] Unexpected response format:", response.data);
+      return [];
+    }
+
+    const automations = response.data;
+    automations.forEach((a) => {
+      a.deploymentId = a.deployment_id;
+      a.automationUrl = a.automation_url;
+    });
+    return automations;
   } else {
     throw new Error(`Failed to get automations from GitOps`);
   }
-}
+};
 
 export const getImages = async (imagesUrl: string, secret: string) => {
   const response = await axios.get(
diff --git a/Extension/src/views/automations_view.ts b/Extension/src/views/automations_view.ts
index 95f4852..d35647f 100644
--- a/Extension/src/views/automations_view.ts
+++ b/Extension/src/views/automations_view.ts
@@ -31,7 +31,8 @@ export class AutomationsViewProvider implements vscode.TreeDataProvider<vscode.T
                 automation.state,
                 automation.status,
                 automation.deploymentId,
-                automation.active
+                automation.active,
+                automation.automationUrl
             )
         );
 
@@ -56,7 +57,8 @@ export class AutomationItem extends vscode.TreeItem {
         public readonly state: string,
         public readonly status: string,
         public readonly deploymentId: string,
-        public readonly active: boolean = false
+        public readonly active: boolean = false,
+        public readonly automationUrl: string
     ) {
         super(name, vscode.TreeItemCollapsibleState.None);
         this.tooltip = `${this.name}`;
@@ -71,7 +73,9 @@ export class AutomationItem extends vscode.TreeItem {
 
     private getContextValue(): string {
         const status = this.active ? 'active' : 'inactive';
-        return `automation,${status},${this.state ?? 'exited'}`;
+        const state = this.state ?? 'exited';
+        const urlStatus = this.automationUrl ? 'url' : 'nourl';
+        return `automation,${status},${state} urlStatus:${urlStatus}`;
     }
 
     private statusIcon(status?: string): vscode.ThemeIcon {


Reviews:
user: login: timthelion
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-11T11:30:34Z
user: login: timthelion
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-11T11:31:52Z
user: login: timthelion
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-04-28T10:35:38Z

Review comments:
diff_hunk: @@ -29,263 +38,371 @@ export const outputChannelsMap = new Map<string, vscode.OutputChannel>();
 // Store the refresh interval ID
 export let automationRefreshInterval: NodeJS.Timer | undefined;
 
-export function setAutomationRefreshInterval(interval: NodeJS.Timer | undefined) {
-    if (automationRefreshInterval) {
-        clearInterval(automationRefreshInterval);
-    }
-    automationRefreshInterval = interval;
+export function setAutomationRefreshInterval(
+  interval: NodeJS.Timer | undefined,
+) {
+  if (automationRefreshInterval) {
+    clearInterval(automationRefreshInterval);
+  }
+  automationRefreshInterval = interval;
 }
 
 /**
  * This method is called by VSC when extension is activated.
  */
 export function activate(context: vscode.ExtensionContext) {
-    // Create and show output channel immediately
-    outputChannel = vscode.window.createOutputChannel('BitSwan');
-    outputChannel.show(true); // true forces the output channel to take focus
-
-    outputChannel.appendLine('=====================================');
-    outputChannel.appendLine('BitSwan Extension Activation Start');
-    outputChannel.appendLine(`Activation Time: ${new Date().toISOString()}`);
-    outputChannel.appendLine('=====================================');
-
-    // Add console.log for debugging in Debug Console
-    console.log('BitSwan Extension Activating - Debug Console Test');
-
-    if (process.env.BITSWAN_DEPLOY_URL || process.env.BITSWAN_DEPLOY_SECRET) {
-        vscode.commands.executeCommand('bitswan-workspaces.removeView');
-    }
-
-    // Create view providers
-    const automationSourcesProvider = new AutomationSourcesViewProvider(context);
-    const workspacesProvider = new WorkspacesViewProvider(context);
-    const automationsProvider = new AutomationsViewProvider(context);
-    const imageSourcesProvider = new ImageSourcesViewProvider(context);
-    const imagesProvider = new ImagesViewProvider(context);
-
-    // Register views
-    vscode.window.createTreeView('bitswan-automation-sources', {
-        treeDataProvider: automationSourcesProvider,
-    });
-
-    vscode.window.createTreeView('bitswan-workspaces', {
-        treeDataProvider: workspacesProvider,
-    });
-
-    vscode.window.createTreeView('bitswan-automations', {
-        treeDataProvider: automationsProvider,
-    });
-
-    vscode.window.createTreeView('bitswan-image-sources', {
-        treeDataProvider: imageSourcesProvider,
-    });
-
-    vscode.window.createTreeView('bitswan-images', {
-        treeDataProvider: imagesProvider,
-    });
-
-    let deployFromToolbarCommand = vscode.commands.registerCommand('bitswan.deployAutomationFromToolbar', 
-        async (item: string) => deploymentCommands.deployFromNotebookToolbarCommand(context, item, "automations"));
-
-    // Register commands using the new command modules
-    let deployCommand = vscode.commands.registerCommand('bitswan.deployAutomation', 
-        async (item: FolderItem) => deploymentCommands.deployCommand(context, automationSourcesProvider, item, "automations"));
-
-    let buildImageFromToolbarCommand = vscode.commands.registerCommand('bitswan.buildImageFromToolbar', 
-        async (item: vscode.Uri) => deploymentCommands.deployFromToolbarCommand(context, item, "images"));
- 
-    let buildImageCommand = vscode.commands.registerCommand('bitswan.buildImage', 
-        async (item: FolderItem) => deploymentCommands.deployCommand(context, automationSourcesProvider, item, "images"));
-    
-    let addGitOpsCommand = vscode.commands.registerCommand('bitswan.addGitOps', 
-        async () => workspaceCommands.addGitOpsCommand(context, workspacesProvider));
-    
-    let editGitOpsCommand = vscode.commands.registerCommand('bitswan.editGitOps', 
-        async (item: GitOpsItem) => workspaceCommands.editGitOpsCommand(context, workspacesProvider, item));
-    
-    let deleteGitOpsCommand = vscode.commands.registerCommand('bitswan.deleteGitOps', 
-        async (item: GitOpsItem) => workspaceCommands.deleteGitOpsCommand(context, workspacesProvider, item));
-    
-    let activateGitOpsCommand = vscode.commands.registerCommand('bitswan.activateGitOps', 
-        async (item: GitOpsItem) => {
-            await workspaceCommands.activateGitOpsCommand(context, workspacesProvider, item, automationsProvider); // Refresh automations when GitOps instance is activated
-        });
-    
-    let refreshAutomationsCommand = vscode.commands.registerCommand('bitswan.refreshAutomations', 
-        async () => automationCommands.refreshAutomationsCommand(context, automationsProvider));
-
-    let refreshImagesCommand = vscode.commands.registerCommand('bitswan.refreshImages', 
-        async () => imageCommands.refreshImagesCommand(context, imagesProvider));
- 
-    
-    let startAutomationCommand = vscode.commands.registerCommand('bitswan.startAutomation', 
-        async (item: AutomationItem) => itemCommands.makeItemCommand({
-            title: `Starting Automation ${item.name}`,
-            initialProgress: 'Sending request to GitOps...',
-            urlPath: 'start',
-            apiFunction: startAutomation,
-            successProgress: `Automation ${item.name} started successfully`,
-            successMessage: `Automation ${item.name} started successfully`,
-            errorMessage: `Failed to start automation ${item.name}:`,
-            errorLogPrefix: 'Automation Start Error:'
-        })(context, automationsProvider, item));
-    
-    let stopAutomationCommand = vscode.commands.registerCommand('bitswan.stopAutomation', 
-        async (item: AutomationItem) => itemCommands.makeItemCommand({
-            title: `Stopping Automation ${item.name}`,
-            initialProgress: 'Sending request to GitOps...',
-            urlPath: 'stop',
-            apiFunction: stopAutomation,
-            successProgress: `Automation ${item.name} stopped successfully`,
-            successMessage: `Automation ${item.name} stopped successfully`,
-            errorMessage: `Failed to stop automation ${item.name}:`,
-            errorLogPrefix: 'Automation Stop Error:'
-        })(context, automationsProvider, item));
-    
-    let restartAutomationCommand = vscode.commands.registerCommand('bitswan.restartAutomation',     
-        async (item: AutomationItem) => itemCommands.makeItemCommand({
-            title: `Restarting Automation ${item.name}`,
-            initialProgress: 'Sending request to GitOps...',
-            urlPath: 'restart',
-            apiFunction: restartAutomation,
-            successProgress: `Automation ${item.name} restarted successfully`,
-            successMessage: `Automation ${item.name} restarted successfully`,
-            errorMessage: `Failed to restart automation ${item.name}:`,
-            errorLogPrefix: 'Automation Restart Error:'
-        })(context, automationsProvider, item));
-    
-    let showAutomationLogsCommand = vscode.commands.registerCommand('bitswan.showAutomationLogs', 
-        async (item: AutomationItem) => automationCommands.showAutomationLogsCommand(context, automationsProvider, item));
-
-    let showImageLogsCommand = vscode.commands.registerCommand('bitswan.showImageLogs', 
-        async (item: ImageItem) => imageCommands.showImageLogsCommand(context, imagesProvider, item));
-
-
-    let activateAutomationCommand = vscode.commands.registerCommand('bitswan.activateAutomation', 
-        async (item: AutomationItem) => itemCommands.makeItemCommand({
-            title: `Activating Automation ${item.name}`,
-            initialProgress: 'Sending request to GitOps...',
-            urlPath: 'activate',
-            apiFunction: activateAutomation,
-            successProgress: `Automation ${item.name} activated successfully`,
-            successMessage: `Automation ${item.name} activated successfully`,
-            errorMessage: `Failed to activate automation ${item.name}:`,
-            errorLogPrefix: 'Automation Activate Error:'
-        })(context, automationsProvider, item));
-    
-    let deactivateAutomationCommand = vscode.commands.registerCommand('bitswan.deactivateAutomation', 
-        async (item: AutomationItem) => itemCommands.makeItemCommand({
-            title: `Deactivating Automation ${item.name}`,
-            initialProgress: 'Sending request to GitOps...',
-            urlPath: 'deactivate',
-            apiFunction: deactivateAutomation,
-            successProgress: `Automation ${item.name} deactivated successfully`,
-            successMessage: `Automation ${item.name} deactivated successfully`,
-            errorMessage: `Failed to deactivate automation ${item.name}:`,
-            errorLogPrefix: 'Automation Deactivate Error:'
-        })(context, automationsProvider, item));
-    
-    let deleteAutomationCommand = vscode.commands.registerCommand('bitswan.deleteAutomation', 
-        async (item: AutomationItem) => itemCommands.makeItemCommand({
-            title: `Deleting Automation ${item.name}`,
-            initialProgress: 'Sending request to GitOps...',
-            urlPath: '',
-            apiFunction: deleteAutomation,
-            successProgress: `Automation ${item.name} deleted successfully`,
-            successMessage: `Automation ${item.name} deleted successfully`,
-            errorMessage: `Failed to delete automation ${item.name}:`,
-            errorLogPrefix: 'Automation Delete Error:',
-            prompt: true
-        })(context, automationsProvider, item));
-
-    let deleteImageCommand = vscode.commands.registerCommand('bitswan.deleteImage', 
-        async (item: ImageItem) => itemCommands.makeItemCommand({
-            title: `Removing image ${item.name}`,
-            initialProgress: 'Sending request to GitOps...',
-            urlPath: '',
-            apiFunction: deleteImage,
-            successProgress: `Image ${item.name} deleted successfully`,
-            successMessage: `Image ${item.name} deleted successfully`,
-            errorMessage: `Failed to delete image ${item.name}:`,
-            errorLogPrefix: 'Image Delete Error:',
-            prompt: false 
-        })(context, imagesProvider, item));
- 
-    
-    // Register all commands
-    context.subscriptions.push(deployCommand);
-    context.subscriptions.push(deployFromToolbarCommand);
-    context.subscriptions.push(buildImageCommand);
-    context.subscriptions.push(buildImageFromToolbarCommand);
-    context.subscriptions.push(addGitOpsCommand);
-    context.subscriptions.push(editGitOpsCommand);
-    context.subscriptions.push(deleteGitOpsCommand);
-    context.subscriptions.push(activateGitOpsCommand);
-    context.subscriptions.push(refreshAutomationsCommand);
-    context.subscriptions.push(refreshImagesCommand);
-    context.subscriptions.push(restartAutomationCommand);
-    context.subscriptions.push(startAutomationCommand);
-    context.subscriptions.push(stopAutomationCommand);
-    context.subscriptions.push(showAutomationLogsCommand);
-    context.subscriptions.push(activateAutomationCommand);
-    context.subscriptions.push(deactivateAutomationCommand);
-    context.subscriptions.push(deleteAutomationCommand);
-    context.subscriptions.push(deleteImageCommand);
-
-    // Refresh the tree views when files change in the workspace
-    const watcher = vscode.workspace.createFileSystemWatcher('**/*');
-    watcher.onDidCreate(() => automationSourcesProvider.refresh());
-    watcher.onDidCreate(() => imageSourcesProvider.refresh());
-    watcher.onDidDelete(() => automationSourcesProvider.refresh());
-    watcher.onDidDelete(() => imageSourcesProvider.refresh());
-    watcher.onDidChange(() => automationSourcesProvider.refresh());
-    watcher.onDidChange(() => imageSourcesProvider.refresh());;
-    
-
-    const activeGitOpsInstance = context.globalState.get<GitOpsItem>('activeGitOpsInstance');
-    if (activeGitOpsInstance) {
-        workspaceCommands.activateGitOpsCommand(context, workspacesProvider, activeGitOpsInstance, automationsProvider);
-        automationsProvider.refresh();
-    } else if (process.env.BITSWAN_DEPLOY_URL && process.env.BITSWAN_DEPLOY_SECRET) {
-        const activeGitOpsInstance = new GitOpsItem(
-            'Active GitOps Instance',
-            process.env.BITSWAN_DEPLOY_URL,
-            process.env.BITSWAN_DEPLOY_SECRET,
-            true
-        );
-        workspaceCommands.activateGitOpsCommand(context, workspacesProvider, activeGitOpsInstance, automationsProvider);
-        automationsProvider.refresh();
-    }
-
-    context.subscriptions.push(watcher);
-
-    outputChannel.appendLine('Tree views registered');
+  // Create and show output channel immediately
+  outputChannel = vscode.window.createOutputChannel("BitSwan");
+  outputChannel.show(true); // true forces the output channel to take focus
+
+  outputChannel.appendLine("=====================================");
+  outputChannel.appendLine("BitSwan Extension Activation Start");
+  outputChannel.appendLine(`Activation Time: ${new Date().toISOString()}`);
+  outputChannel.appendLine("=====================================");
+
+  // Add console.log for debugging in Debug Console
+  console.log("BitSwan Extension Activating - Debug Console Test");
+
+  if (process.env.BITSWAN_DEPLOY_URL || process.env.BITSWAN_DEPLOY_SECRET) {
+    vscode.commands.executeCommand("bitswan-workspaces.removeView");
+  }
+
+  // Create view providers
+  const automationSourcesProvider = new AutomationSourcesViewProvider(context);
+  const workspacesProvider = new WorkspacesViewProvider(context);
+  const automationsProvider = new AutomationsViewProvider(context);
+  const imageSourcesProvider = new ImageSourcesViewProvider(context);
+  const imagesProvider = new ImagesViewProvider(context);
+
+  // Register views
+  vscode.window.createTreeView("bitswan-automation-sources", {
+    treeDataProvider: automationSourcesProvider,
+  });
+
+  vscode.window.createTreeView("bitswan-workspaces", {
+    treeDataProvider: workspacesProvider,
+  });
+
+  vscode.window.createTreeView("bitswan-automations", {
+    treeDataProvider: automationsProvider,
+  });
+
+  vscode.window.createTreeView("bitswan-image-sources", {
+    treeDataProvider: imageSourcesProvider,
+  });
+
+  vscode.window.createTreeView("bitswan-images", {
+    treeDataProvider: imagesProvider,
+  });
+
+  let deployFromToolbarCommand = vscode.commands.registerCommand(
+    "bitswan.deployAutomationFromToolbar",
+    async (item: string) =>
+      deploymentCommands.deployFromNotebookToolbarCommand(
+        context,
+        item,
+        "automations",
+      ),
+  );
+
+  // Register commands using the new command modules
+  let deployCommand = vscode.commands.registerCommand(
+    "bitswan.deployAutomation",
+    async (item: FolderItem) =>
+      deploymentCommands.deployCommand(
+        context,
+        automationSourcesProvider,
+        item,
+        "automations",
+      ),
+  );
+
+  let buildImageFromToolbarCommand = vscode.commands.registerCommand(
+    "bitswan.buildImageFromToolbar",
+    async (item: vscode.Uri) =>
+      deploymentCommands.deployFromToolbarCommand(context, item, "images"),
+  );
+
+  let buildImageCommand = vscode.commands.registerCommand(
+    "bitswan.buildImage",
+    async (item: FolderItem) =>
+      deploymentCommands.deployCommand(
+        context,
+        automationSourcesProvider,
+        item,
+        "images",
+      ),
+  );
+
+  let addGitOpsCommand = vscode.commands.registerCommand(
+    "bitswan.addGitOps",
+    async () => workspaceCommands.addGitOpsCommand(context, workspacesProvider),
+  );
+
+  let editGitOpsCommand = vscode.commands.registerCommand(
+    "bitswan.editGitOps",
+    async (item: GitOpsItem) =>
+      workspaceCommands.editGitOpsCommand(context, workspacesProvider, item),
+  );
+
+  let deleteGitOpsCommand = vscode.commands.registerCommand(
+    "bitswan.deleteGitOps",
+    async (item: GitOpsItem) =>
+      workspaceCommands.deleteGitOpsCommand(context, workspacesProvider, item),
+  );
+
+  let activateGitOpsCommand = vscode.commands.registerCommand(
+    "bitswan.activateGitOps",
+    async (item: GitOpsItem) => {
+      await workspaceCommands.activateGitOpsCommand(
+        context,
+        workspacesProvider,
+        item,
+        automationsProvider,
+      ); // Refresh automations when GitOps instance is activated
+    },
+  );
+
+  let refreshAutomationsCommand = vscode.commands.registerCommand(
+    "bitswan.refreshAutomations",
+    async () =>
+      automationCommands.refreshAutomationsCommand(
+        context,
+        automationsProvider,
+      ),
+  );
+
+  let refreshImagesCommand = vscode.commands.registerCommand(
+    "bitswan.refreshImages",
+    async () => imageCommands.refreshImagesCommand(context, imagesProvider),
+  );
+
+  let openExternalUrlCommand = vscode.commands.registerCommand(
+    "bitswan.openExternalUrl",
+    async (item: AutomationItem) => {
+      const url = item.deploymentUrl;
+      try {
+        await vscode.env.openExternal(vscode.Uri.parse(url));
+        vscode.window.showInformationMessage(`Opened ${item.name} in browser.`);
+      } catch (err) {
+        vscode.window.showErrorMessage(`Failed to open URL: ${url}`);
+        outputChannel.appendLine(`Error opening URL: ${err}`);
+      }
+    },
+  );
+
+  let startAutomationCommand = vscode.commands.registerCommand(
+    "bitswan.startAutomation",
+    async (item: AutomationItem) =>
+      itemCommands.makeItemCommand({
+        title: `Starting Automation ${item.name}`,
+        initialProgress: "Sending request to GitOps...",
+        urlPath: "start",
+        apiFunction: startAutomation,
+        successProgress: `Automation ${item.name} started successfully`,
+        successMessage: `Automation ${item.name} started successfully`,
+        errorMessage: `Failed to start automation ${item.name}:`,
+        errorLogPrefix: "Automation Start Error:",
+      })(context, automationsProvider, item),
+  );
+
+  let stopAutomationCommand = vscode.commands.registerCommand(
+    "bitswan.stopAutomation",
+    async (item: AutomationItem) =>
+      itemCommands.makeItemCommand({
+        title: `Stopping Automation ${item.name}`,
+        initialProgress: "Sending request to GitOps...",
+        urlPath: "stop",
+        apiFunction: stopAutomation,
+        successProgress: `Automation ${item.name} stopped successfully`,
+        successMessage: `Automation ${item.name} stopped successfully`,
+        errorMessage: `Failed to stop automation ${item.name}:`,
+        errorLogPrefix: "Automation Stop Error:",
+      })(context, automationsProvider, item),
+  );
+
+  let restartAutomationCommand = vscode.commands.registerCommand(
+    "bitswan.restartAutomation",
+    async (item: AutomationItem) =>
+      itemCommands.makeItemCommand({
+        title: `Restarting Automation ${item.name}`,
+        initialProgress: "Sending request to GitOps...",
+        urlPath: "restart",
+        apiFunction: restartAutomation,
+        successProgress: `Automation ${item.name} restarted successfully`,
+        successMessage: `Automation ${item.name} restarted successfully`,
+        errorMessage: `Failed to restart automation ${item.name}:`,
+        errorLogPrefix: "Automation Restart Error:",
+      })(context, automationsProvider, item),
+  );
+
+  let showAutomationLogsCommand = vscode.commands.registerCommand(
+    "bitswan.showAutomationLogs",
+    async (item: AutomationItem) =>
+      automationCommands.showAutomationLogsCommand(
+        context,
+        automationsProvider,
+        item,
+      ),
+  );
+
+  let showImageLogsCommand = vscode.commands.registerCommand(
+    "bitswan.showImageLogs",
+    async (item: ImageItem) =>
+      imageCommands.showImageLogsCommand(context, imagesProvider, item),
+  );
+
+  let activateAutomationCommand = vscode.commands.registerCommand(
+    "bitswan.activateAutomation",
+    async (item: AutomationItem) =>
+      itemCommands.makeItemCommand({
+        title: `Activating Automation ${item.name}`,
+        initialProgress: "Sending request to GitOps...",
+        urlPath: "activate",
+        apiFunction: activateAutomation,
+        successProgress: `Automation ${item.name} activated successfully`,
+        successMessage: `Automation ${item.name} activated successfully`,
+        errorMessage: `Failed to activate automation ${item.name}:`,
+        errorLogPrefix: "Automation Activate Error:",
+      })(context, automationsProvider, item),
+  );
+
+  let deactivateAutomationCommand = vscode.commands.registerCommand(
+    "bitswan.deactivateAutomation",
+    async (item: AutomationItem) =>
+      itemCommands.makeItemCommand({
+        title: `Deactivating Automation ${item.name}`,
+        initialProgress: "Sending request to GitOps...",
+        urlPath: "deactivate",
+        apiFunction: deactivateAutomation,
+        successProgress: `Automation ${item.name} deactivated successfully`,
+        successMessage: `Automation ${item.name} deactivated successfully`,
+        errorMessage: `Failed to deactivate automation ${item.name}:`,
+        errorLogPrefix: "Automation Deactivate Error:",
+      })(context, automationsProvider, item),
+  );
+
+  let deleteAutomationCommand = vscode.commands.registerCommand(
+    "bitswan.deleteAutomation",
+    async (item: AutomationItem) =>
+      itemCommands.makeItemCommand({
+        title: `Deleting Automation ${item.name}`,
+        initialProgress: "Sending request to GitOps...",
+        urlPath: "",
+        apiFunction: deleteAutomation,
+        successProgress: `Automation ${item.name} deleted successfully`,
+        successMessage: `Automation ${item.name} deleted successfully`,
+        errorMessage: `Failed to delete automation ${item.name}:`,
+        errorLogPrefix: "Automation Delete Error:",
+        prompt: true,
+      })(context, automationsProvider, item),
+  );
+
+  let deleteImageCommand = vscode.commands.registerCommand(
+    "bitswan.deleteImage",
+    async (item: ImageItem) =>
+      itemCommands.makeItemCommand({
+        title: `Removing image ${item.name}`,
+        initialProgress: "Sending request to GitOps...",
+        urlPath: "",
+        apiFunction: deleteImage,
+        successProgress: `Image ${item.name} deleted successfully`,
+        successMessage: `Image ${item.name} deleted successfully`,
+        errorMessage: `Failed to delete image ${item.name}:`,
+        errorLogPrefix: "Image Delete Error:",
+        prompt: false,
+      })(context, imagesProvider, item),
+  );
+
+  // Register all commands
+  context.subscriptions.push(deployCommand);
+  context.subscriptions.push(deployFromToolbarCommand);
+  context.subscriptions.push(buildImageCommand);
+  context.subscriptions.push(buildImageFromToolbarCommand);
+  context.subscriptions.push(addGitOpsCommand);
+  context.subscriptions.push(editGitOpsCommand);
+  context.subscriptions.push(deleteGitOpsCommand);
+  context.subscriptions.push(activateGitOpsCommand);
+  context.subscriptions.push(refreshAutomationsCommand);
+  context.subscriptions.push(refreshImagesCommand);
+  context.subscriptions.push(openExternalUrlCommand);
+  context.subscriptions.push(restartAutomationCommand);
+  context.subscriptions.push(startAutomationCommand);
+  context.subscriptions.push(stopAutomationCommand);
+  context.subscriptions.push(showAutomationLogsCommand);
+  context.subscriptions.push(activateAutomationCommand);
+  context.subscriptions.push(deactivateAutomationCommand);
+  context.subscriptions.push(deleteAutomationCommand);
+  context.subscriptions.push(deleteImageCommand);
+
+  // Refresh the tree views when files change in the workspace
+  const watcher = vscode.workspace.createFileSystemWatcher("**/*");
+  watcher.onDidCreate(() => automationSourcesProvider.refresh());
+  watcher.onDidCreate(() => imageSourcesProvider.refresh());
+  watcher.onDidDelete(() => automationSourcesProvider.refresh());
+  watcher.onDidDelete(() => imageSourcesProvider.refresh());
+  watcher.onDidChange(() => automationSourcesProvider.refresh());
+  watcher.onDidChange(() => imageSourcesProvider.refresh());
+
+  const activeGitOpsInstance = context.globalState.get<GitOpsItem>(
+    "activeGitOpsInstance",
+  );
+  if (activeGitOpsInstance) {
+    workspaceCommands.activateGitOpsCommand(
+      context,
+      workspacesProvider,
+      activeGitOpsInstance,
+      automationsProvider,
+    );
+    automationsProvider.refresh();
+  } else if (
+    process.env.BITSWAN_DEPLOY_URL &&
+    process.env.BITSWAN_DEPLOY_SECRET
+  ) {
+    const activeGitOpsInstance = new GitOpsItem(
+      "Active GitOps Instance",
+      process.env.BITSWAN_DEPLOY_URL,
+      process.env.BITSWAN_DEPLOY_SECRET,
+      true,
+    );
+    workspaceCommands.activateGitOpsCommand(
+      context,
+      workspacesProvider,
+      activeGitOpsInstance,
+      automationsProvider,
+    );
+    automationsProvider.refresh();
+  }
+
+  context.subscriptions.push(watcher);
+
+  outputChannel.appendLine("Tree views registered");
 }
 
 /**
  * This method is called when the extension is deactivated
  */
 export function deactivate() {
-    // Clean up the refresh interval
path: Extension/src/extension.ts
user: login: timthelion
body: why change kndent level?
created_at: 2025-04-11T11:30:34Z
author_association: CONTRIBUTOR
diff_hunk: @@ -29,263 +38,371 @@ export const outputChannelsMap = new Map<string, vscode.OutputChannel>();
 // Store the refresh interval ID
 export let automationRefreshInterval: NodeJS.Timer | undefined;
 
-export function setAutomationRefreshInterval(interval: NodeJS.Timer | undefined) {
path: Extension/src/extension.ts
user: login: timthelion
body: what changed here even???
created_at: 2025-04-11T11:31:52Z
author_association: CONTRIBUTOR

PR comments:
user: login: timthelion
created_at: 2025-04-11T11:34:08Z
body: seems like there are some formatting changes or some malfunction with the diff...
author_association: CONTRIBUTOR
user: login: JachymDolezal
created_at: 2025-04-11T12:31:23Z
body: Yes my zed editor had built-in formatting for all kinds of programming languages. I will try to revert the formatting changes.
author_association: CONTRIBUTOR