diff --git a/package.json b/package.json
index 441f4476e..148508066 100644
--- a/package.json
+++ b/package.json
@@ -277,6 +277,12 @@
"command": "containerApps.walkthrough.cleanUpResources",
"title": "%containerApps.walkthrough.cleanUpResources%",
"category": "Azure Container Apps"
+ },
+ {
+ "command": "containerapps.toggleEnvironmentVariableVisibility",
+ "title": "%containerapps.toggleEnvironmentVariableVisibility%",
+ "category": "Azure Container Apps",
+ "icon": "$(eye)"
}
],
"submenus": [
@@ -526,6 +532,11 @@
"command": "containerApps.disconnectRepo",
"when": "view =~ /(azureResourceGroups|azureFocusView)/ && viewItem =~ /actionsConnected:true(.*)containerAppsActionsItem/i",
"group": "1@2"
+ },
+ {
+ "command": "containerapps.toggleEnvironmentVariableVisibility",
+ "when": "view =~ /(azureResourceGroups|azureFocusView)/ && viewItem =~ /environmentVariableItem/i",
+ "group": "inline"
}
],
"commandPalette": [
diff --git a/package.nls.json b/package.nls.json
index 69a27ec60..31b09d08c 100644
--- a/package.nls.json
+++ b/package.nls.json
@@ -61,6 +61,7 @@
"containerApps.deploymentConfiguration.containerApp": "The name of the target container app.",
"containerApps.deploymentConfiguration.containerRegistry": "The name of the container registry to use for storing and building images.",
"containerApps.deploymentConfigurations": "A list of saved deployment configurations used for quickly redeploying a workspace project to a container app.",
+ "containerApp.toggleEnvironmentVariableVisibility": "Toggle Environment Variable Visibility.",
"containerApps.walkthrough.gettingStarted.internal": "Open Walkthrough",
"containerApps.walkthrough.gettingStarted.title": "Getting Started with Azure Container Apps",
"containerApps.walkthrough.gettingStarted.description": "Learn to deploy a containerized application to Azure Container Apps using a sample GitHub repository.",
diff --git a/resources/containers.svg b/resources/containers.svg
new file mode 100644
index 000000000..fd6a1efcf
--- /dev/null
+++ b/resources/containers.svg
@@ -0,0 +1,33 @@
+
+
+
+
diff --git a/src/commands/registerCommands.ts b/src/commands/registerCommands.ts
index 8afc2e01f..3f9331a56 100644
--- a/src/commands/registerCommands.ts
+++ b/src/commands/registerCommands.ts
@@ -3,7 +3,8 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
-import { registerCommand, registerCommandWithTreeNodeUnwrapping, registerErrorHandler, registerReportIssueCommand } from '@microsoft/vscode-azext-utils';
+import { registerCommand, registerCommandWithTreeNodeUnwrapping, registerErrorHandler, registerReportIssueCommand, type IActionContext } from '@microsoft/vscode-azext-utils';
+import { type EnvironmentVariableItem } from '../tree/containers/EnvironmentVariableItem';
import { browseContainerAppNode } from './browseContainerApp';
import { createContainerApp } from './createContainerApp/createContainerApp';
import { createManagedEnvironment } from './createManagedEnvironment/createManagedEnvironment';
@@ -58,6 +59,10 @@ export function registerCommands(): void {
registerCommandWithTreeNodeUnwrapping('containerApps.editContainerApp', editContainerApp);
registerCommandWithTreeNodeUnwrapping('containerApps.openConsoleInPortal', openConsoleInPortal);
registerCommandWithTreeNodeUnwrapping('containerApps.updateImage', updateImage);
+ registerCommandWithTreeNodeUnwrapping('containerapps.toggleEnvironmentVariableVisibility',
+ async (context: IActionContext, item: EnvironmentVariableItem) => {
+ await item.toggleValueVisibility(context);
+ });
// deploy
registerCommandWithTreeNodeUnwrapping('containerApps.deployImageApi', deployImageApi);
diff --git a/src/tree/containers/ContainerItem.ts b/src/tree/containers/ContainerItem.ts
new file mode 100644
index 000000000..3a539e0de
--- /dev/null
+++ b/src/tree/containers/ContainerItem.ts
@@ -0,0 +1,51 @@
+/*---------------------------------------------------------------------------------------------
+* Copyright (c) Microsoft Corporation. All rights reserved.
+* Licensed under the MIT License. See License.md in the project root for license information.
+*--------------------------------------------------------------------------------------------*/
+
+import { type Container, type Revision } from "@azure/arm-appcontainers";
+import { nonNullProp, nonNullValue, type TreeElementBase } from "@microsoft/vscode-azext-utils";
+import { type AzureSubscription, type ViewPropertiesModel } from "@microsoft/vscode-azureresources-api";
+import { TreeItemCollapsibleState, type TreeItem } from "vscode";
+import { getParentResource } from "../../utils/revisionDraftUtils";
+import { type ContainerAppModel } from "../ContainerAppItem";
+import { type RevisionsItemModel } from "../revisionManagement/RevisionItem";
+import { EnvironmentVariablesItem } from "./EnvironmentVariablesItem";
+import { ImageItem } from "./ImageItem";
+
+export class ContainerItem implements RevisionsItemModel {
+ id: string;
+ label: string;
+ static readonly contextValue: string = 'containerItem';
+ static readonly contextValueRegExp: RegExp = new RegExp(ContainerItem.contextValue);
+
+ constructor(readonly subscription: AzureSubscription, readonly containerApp: ContainerAppModel, readonly revision: Revision, readonly container: Container) {
+ this.id = `${this.parentResource.id}/${container.name}`;
+ this.label = nonNullValue(this.container.name);
+ }
+
+ getTreeItem(): TreeItem {
+ return {
+ id: this.id,
+ label: `${this.container.name}`,
+ contextValue: ContainerItem.contextValue,
+ collapsibleState: TreeItemCollapsibleState.Collapsed,
+ }
+ }
+
+ getChildren(): TreeElementBase[] {
+ return [
+ new ImageItem(this.subscription, this.containerApp, this.revision, this.id, this.container),
+ new EnvironmentVariablesItem(this.subscription, this.containerApp, this.revision, this.id, this.container)
+ ];
+ }
+
+ private get parentResource(): ContainerAppModel | Revision {
+ return getParentResource(this.containerApp, this.revision);
+ }
+
+ viewProperties: ViewPropertiesModel = {
+ data: this.container,
+ label: nonNullProp(this.container, 'name'),
+ }
+}
diff --git a/src/tree/containers/ContainersItem.ts b/src/tree/containers/ContainersItem.ts
new file mode 100644
index 000000000..0bdbd16f3
--- /dev/null
+++ b/src/tree/containers/ContainersItem.ts
@@ -0,0 +1,89 @@
+/*---------------------------------------------------------------------------------------------
+* Copyright (c) Microsoft Corporation. All rights reserved.
+* Licensed under the MIT License. See License.md in the project root for license information.
+*--------------------------------------------------------------------------------------------*/
+
+import { KnownActiveRevisionsMode, type Container, type Revision } from "@azure/arm-appcontainers";
+import { nonNullValue, nonNullValueAndProp, type TreeElementBase } from "@microsoft/vscode-azext-utils";
+import { type AzureSubscription, type ViewPropertiesModel } from "@microsoft/vscode-azureresources-api";
+import * as deepEqual from 'deep-eql';
+import { TreeItemCollapsibleState, type TreeItem } from "vscode";
+import { ext } from "../../extensionVariables";
+import { localize } from "../../utils/localize";
+import { getParentResource } from "../../utils/revisionDraftUtils";
+import { treeUtils } from "../../utils/treeUtils";
+import { type ContainerAppModel } from "../ContainerAppItem";
+import { RevisionDraftDescendantBase } from "../revisionManagement/RevisionDraftDescendantBase";
+import { RevisionDraftItem } from "../revisionManagement/RevisionDraftItem";
+import { ContainerItem } from "./ContainerItem";
+import { EnvironmentVariablesItem } from "./EnvironmentVariablesItem";
+import { ImageItem } from "./ImageItem";
+
+export class ContainersItem extends RevisionDraftDescendantBase {
+ id: string;
+ label: string;
+ private containers: Container[] = [];
+
+ constructor(public readonly subscription: AzureSubscription,
+ public readonly containerApp: ContainerAppModel,
+ public readonly revision: Revision,) {
+ super(subscription, containerApp, revision);
+ this.id = `${this.parentResource.id}/containers`;
+ this.containers = nonNullValue(revision.template?.containers);
+ this.label = this.containers.length === 1 ? localize('container', 'Container') : localize('containers', 'Containers');
+ }
+
+ getChildren(): TreeElementBase[] {
+ if (this.containers.length === 1) {
+ return [new ImageItem(this.subscription, this.containerApp, this.revision, this.id, this.containers[0]),
+ new EnvironmentVariablesItem(this.subscription, this.containerApp, this.revision, this.id, this.containers[0])];
+ }
+ return nonNullValue(this.containers?.map(container => new ContainerItem(this.subscription, this.containerApp, this.revision, container)));
+ }
+
+ getTreeItem(): TreeItem {
+ return {
+ id: this.id,
+ label: this.label,
+ iconPath: treeUtils.getIconPath('containers'),
+ collapsibleState: TreeItemCollapsibleState.Collapsed
+ }
+ }
+
+ private get parentResource(): ContainerAppModel | Revision {
+ return getParentResource(this.containerApp, this.revision);
+ }
+
+ protected setProperties(): void {
+ this.label = this.containers.length === 1 ? localize('container', 'Container') : localize('containers', 'Containers');
+ this.containers = nonNullValueAndProp(this.parentResource.template, 'containers');
+ }
+
+ protected setDraftProperties(): void {
+ this.label = this.containers.length === 1 ? localize('container*', 'Container*') : localize('containers*', 'Containers*');
+ this.containers = nonNullValueAndProp(ext.revisionDraftFileSystem.parseRevisionDraft(this), 'containers');
+ }
+
+ viewProperties: ViewPropertiesModel = {
+ label: 'Containers',
+ getData: async () => {
+ return this.containers.length === 1 ? this.containers[0] : JSON.stringify(this.containers)
+ }
+ }
+
+ hasUnsavedChanges(): boolean {
+ // We only care about showing changes to descendants of the revision draft item when in multiple revisions mode
+ if (this.containerApp.revisionsMode === KnownActiveRevisionsMode.Multiple && !RevisionDraftItem.hasDescendant(this)) {
+ return false;
+ }
+
+ const draftTemplate = ext.revisionDraftFileSystem.parseRevisionDraft(this)?.containers;
+ const currentTemplate = this.parentResource.template?.containers;
+
+ if (!draftTemplate) {
+ return false;
+ }
+
+ return !deepEqual(currentTemplate, draftTemplate);
+ }
+}
diff --git a/src/tree/containers/EnvironmentVariableItem.ts b/src/tree/containers/EnvironmentVariableItem.ts
new file mode 100644
index 000000000..9d0cc19ce
--- /dev/null
+++ b/src/tree/containers/EnvironmentVariableItem.ts
@@ -0,0 +1,49 @@
+/*---------------------------------------------------------------------------------------------
+* Copyright (c) Microsoft Corporation. All rights reserved.
+* Licensed under the MIT License. See License.md in the project root for license information.
+*--------------------------------------------------------------------------------------------*/
+
+import { type Container, type EnvironmentVar, type Revision } from "@azure/arm-appcontainers";
+import { type IActionContext } from "@microsoft/vscode-azext-utils";
+import { type AzureSubscription } from "@microsoft/vscode-azureresources-api";
+import { ThemeIcon, type TreeItem } from "vscode";
+import { ext } from "../../extensionVariables";
+import { localize } from "../../utils/localize";
+import { getParentResource } from "../../utils/revisionDraftUtils";
+import { type ContainerAppModel } from "../ContainerAppItem";
+import { type RevisionsItemModel } from "../revisionManagement/RevisionItem";
+
+export class EnvironmentVariableItem implements RevisionsItemModel {
+ _hideValue: boolean;
+ constructor(public readonly subscription: AzureSubscription,
+ public readonly containerApp: ContainerAppModel,
+ public readonly revision: Revision,
+ readonly containerId: string,
+ readonly container: Container,
+ readonly envVariable: EnvironmentVar) {
+ this._hideValue = true;
+ }
+ id: string = `${this.parentResource.id}/${this.container.image}/${this.envVariable.name}`
+
+ getTreeItem(): TreeItem {
+ return {
+ label: this._hideValue ? `${this.envVariable.name}=Hidden value. Click to view.` : `${this.envVariable.name}=${this.envVariable.value}`,
+ contextValue: 'environmentVariableItem',
+ iconPath: new ThemeIcon('symbol-constant'),
+ command: {
+ command: 'containerapps.toggleEnvironmentVariableVisibility',
+ title: localize('commandtitle', 'Toggle Environment Variable Visibility'),
+ arguments: [this, this._hideValue,]
+ }
+ }
+ }
+
+ public async toggleValueVisibility(_: IActionContext): Promise {
+ this._hideValue = !this._hideValue;
+ ext.branchDataProvider.refresh(this);
+ }
+
+ private get parentResource(): ContainerAppModel | Revision {
+ return getParentResource(this.containerApp, this.revision);
+ }
+}
diff --git a/src/tree/containers/EnvironmentVariablesItem.ts b/src/tree/containers/EnvironmentVariablesItem.ts
new file mode 100644
index 000000000..14c522e02
--- /dev/null
+++ b/src/tree/containers/EnvironmentVariablesItem.ts
@@ -0,0 +1,48 @@
+/*---------------------------------------------------------------------------------------------
+* Copyright (c) Microsoft Corporation. All rights reserved.
+* Licensed under the MIT License. See License.md in the project root for license information.
+*--------------------------------------------------------------------------------------------*/
+
+import { type Container, type Revision } from "@azure/arm-appcontainers";
+import { type TreeElementBase } from "@microsoft/vscode-azext-utils";
+import { type AzureSubscription } from "@microsoft/vscode-azureresources-api";
+import { ThemeIcon, TreeItemCollapsibleState, type TreeItem } from "vscode";
+import { localize } from "../../utils/localize";
+import { getParentResource } from "../../utils/revisionDraftUtils";
+import { type ContainerAppModel } from "../ContainerAppItem";
+import { type RevisionsItemModel } from "../revisionManagement/RevisionItem";
+import { EnvironmentVariableItem } from "./EnvironmentVariableItem";
+
+export class EnvironmentVariablesItem implements RevisionsItemModel {
+ static readonly contextValue: string = 'environmentVariablesItem';
+ static readonly contextValueRegExp: RegExp = new RegExp(EnvironmentVariablesItem.contextValue);
+
+ constructor(public readonly subscription: AzureSubscription,
+ public readonly containerApp: ContainerAppModel,
+ public readonly revision: Revision,
+ readonly containerId: string,
+ readonly container: Container) {
+ }
+ id: string = `${this.parentResource.id}/environmentVariables/${this.container.image}`;
+
+ getTreeItem(): TreeItem {
+ return {
+ id: this.id,
+ label: localize('environmentVariables', 'Environment Variables'),
+ iconPath: new ThemeIcon('settings'),
+ contextValue: EnvironmentVariablesItem.contextValue,
+ collapsibleState: TreeItemCollapsibleState.Collapsed
+ }
+ }
+
+ getChildren(): TreeElementBase[] | undefined {
+ if (!this.container.env) {
+ return;
+ }
+ return this.container.env?.map(env => new EnvironmentVariableItem(this.subscription, this.containerApp, this.revision, this.id, this.container, env));
+ }
+
+ private get parentResource(): ContainerAppModel | Revision {
+ return getParentResource(this.containerApp, this.revision);
+ }
+}
diff --git a/src/tree/containers/ImageItem.ts b/src/tree/containers/ImageItem.ts
new file mode 100644
index 000000000..20b43dd9b
--- /dev/null
+++ b/src/tree/containers/ImageItem.ts
@@ -0,0 +1,64 @@
+/*---------------------------------------------------------------------------------------------
+* Copyright (c) Microsoft Corporation. All rights reserved.
+* Licensed under the MIT License. See License.md in the project root for license information.
+*--------------------------------------------------------------------------------------------*/
+
+import { type Container, type Revision } from "@azure/arm-appcontainers";
+import { createGenericElement, nonNullProp, nonNullValue, type TreeElementBase } from "@microsoft/vscode-azext-utils";
+import { type AzureSubscription, type ViewPropertiesModel } from "@microsoft/vscode-azureresources-api";
+import { ThemeIcon, TreeItemCollapsibleState, type TreeItem } from "vscode";
+import { localize } from "../../utils/localize";
+import { getParentResource } from "../../utils/revisionDraftUtils";
+import { type ContainerAppModel } from "../ContainerAppItem";
+import { type RevisionsItemModel } from "../revisionManagement/RevisionItem";
+
+export class ImageItem implements RevisionsItemModel {
+ static readonly contextValue: string = 'imageItem';
+ static readonly contextValueRegExp: RegExp = new RegExp(ImageItem.contextValue);
+ readonly loginServer = this.container.image?.split('/')[0];
+ readonly imageAndTag = this.container.image?.substring(nonNullValue(this.loginServer?.length) + 1, this.container.image?.length);
+
+ constructor(
+ readonly subscription: AzureSubscription,
+ readonly containerApp: ContainerAppModel,
+ readonly revision: Revision,
+ readonly containerId: string,
+ readonly container: Container) { }
+ id: string = `${this.parentResource.id}/image/${this.imageAndTag}`
+
+ getTreeItem(): TreeItem {
+ return {
+ id: this.id,
+ label: localize('image', 'Image'),
+ iconPath: new ThemeIcon('window'),
+ contextValue: ImageItem.contextValue,
+ collapsibleState: TreeItemCollapsibleState.Collapsed,
+ }
+ }
+
+ getChildren(): TreeElementBase[] {
+ return [
+ createGenericElement({
+ id: `${this.id}/imageName`,
+ label: localize('containerImage', 'Name:'),
+ contextValue: 'containerImageNameItem',
+ description: `${this.imageAndTag}`,
+ }),
+ createGenericElement({
+ id: `${this.id}/imageRegistry`,
+ label: localize('containerImageRegistryItem', 'Registry:'),
+ contextValue: 'containerImageRegistryItem',
+ description: `${this.loginServer}`,
+ })
+ ];
+ }
+
+ private get parentResource(): ContainerAppModel | Revision {
+ return getParentResource(this.containerApp, this.revision);
+ }
+
+ viewProperties: ViewPropertiesModel = {
+ data: this.container,
+ label: nonNullProp(this.container, 'name'),
+ }
+}
diff --git a/src/tree/revisionManagement/RevisionItem.ts b/src/tree/revisionManagement/RevisionItem.ts
index d7e673f8e..f49b88542 100644
--- a/src/tree/revisionManagement/RevisionItem.ts
+++ b/src/tree/revisionManagement/RevisionItem.ts
@@ -12,6 +12,7 @@ import { ext } from "../../extensionVariables";
import { localize } from "../../utils/localize";
import { type ContainerAppModel } from "../ContainerAppItem";
import { type ContainerAppsItem, type TreeElementBase } from "../ContainerAppsBranchDataProvider";
+import { ContainersItem } from "../containers/ContainersItem";
import { ScaleItem } from "../scaling/ScaleItem";
import { RevisionDraftDescendantBase } from "./RevisionDraftDescendantBase";
@@ -68,6 +69,7 @@ export class RevisionItem implements RevisionsItemModel {
static getTemplateChildren(subscription: AzureSubscription, containerApp: ContainerAppModel, revision: Revision): TreeElementBase[] {
return [
+ RevisionDraftDescendantBase.createTreeItem(ContainersItem, subscription, containerApp, revision),
RevisionDraftDescendantBase.createTreeItem(ScaleItem, subscription, containerApp, revision)
];
}