From 89e89c732b0f33e2dd856c8d580513f36e842d17 Mon Sep 17 00:00:00 2001 From: tejhan-diallo Date: Wed, 26 Feb 2025 16:05:04 -0500 Subject: [PATCH 1/3] Replacing toolkit elements with custom dropdown & option logic --- .../AttachAcrToCluster/AttachAcrToCluster.tsx | 9 +- .../AutomatedDeployments.tsx | 22 +- .../AzureServiceOperator.tsx | 3 +- .../src/AzureServiceOperator/Inputs.tsx | 50 ++-- .../src/CreateCluster/CreateCluster.tsx | 8 +- .../src/CreateCluster/CreateClusterInput.tsx | 79 +++--- .../src/CreateFleet/CreateFleetInput.tsx | 52 ++-- webview-ui/src/Detector/Detector.tsx | 6 +- webview-ui/src/Draft/Draft.module.css | 12 + .../Draft/DraftDeployment/DraftDeployment.tsx | 77 +++--- .../Draft/DraftDockerfile/DraftDockerfile.tsx | 24 +- .../src/Draft/DraftWorkflow/DraftWorkflow.tsx | 135 +++++----- .../src/InspektorGadget/GadgetSelector.tsx | 28 +- .../InspektorGadget.module.css | 29 +++ .../src/InspektorGadget/InspektorGadget.tsx | 4 +- .../src/InspektorGadget/NewTraceDialog.tsx | 26 +- webview-ui/src/InspektorGadget/Overview.tsx | 9 +- .../src/InspektorGadget/ResourceSelector.tsx | 76 +++--- .../InspektorGadget/TraceItemSortSelector.tsx | 6 +- webview-ui/src/InspektorGadget/Traces.tsx | 22 +- .../src/KaitoModels/KaitoModels.module.css | 1 + webview-ui/src/Kubectl/CommandInput.tsx | 10 +- webview-ui/src/Kubectl/Kubectl.tsx | 5 +- webview-ui/src/Kubectl/SaveCommandDialog.tsx | 14 +- webview-ui/src/Periscope/ErrorView.tsx | 5 +- webview-ui/src/Periscope/NodeActions.tsx | 9 +- webview-ui/src/Periscope/Periscope.tsx | 7 +- webview-ui/src/Periscope/SuccessView.tsx | 4 +- .../DeleteNodeExplorerDialog.tsx | 5 +- .../src/RetinaCapture/RetinaCapture.tsx | 37 +-- webview-ui/src/TCPDump/CaptureFilters.tsx | 6 +- webview-ui/src/TCPDump/TcpDump.module.css | 9 + webview-ui/src/TCPDump/TcpDump.tsx | 39 ++- .../filterScenarios/SpecificPodFilters.tsx | 23 +- .../filterScenarios/TwoPodsFilters.tsx | 23 +- .../src/components/CustomDropdown.module.css | 95 +++++++ webview-ui/src/components/CustomDropdown.tsx | 242 ++++++++++++++++++ .../src/components/CustomDropdownOption.tsx | 25 ++ webview-ui/src/components/InlineAction.tsx | 7 +- webview-ui/src/components/NodeSelector.tsx | 22 +- .../src/components/ResourceSelector.tsx | 37 +-- .../components/TextWithDropdown.module.css | 27 +- .../src/components/TextWithDropdown.tsx | 62 ++--- .../components/FilePicker.module.css | 9 + .../src/manualTest/components/FilePicker.tsx | 30 ++- 45 files changed, 938 insertions(+), 492 deletions(-) create mode 100644 webview-ui/src/components/CustomDropdown.module.css create mode 100644 webview-ui/src/components/CustomDropdown.tsx create mode 100644 webview-ui/src/components/CustomDropdownOption.tsx diff --git a/webview-ui/src/AttachAcrToCluster/AttachAcrToCluster.tsx b/webview-ui/src/AttachAcrToCluster/AttachAcrToCluster.tsx index 759c05e8b..3c446f9b9 100644 --- a/webview-ui/src/AttachAcrToCluster/AttachAcrToCluster.tsx +++ b/webview-ui/src/AttachAcrToCluster/AttachAcrToCluster.tsx @@ -18,7 +18,6 @@ import { import { AttachAcrToClusterState, stateUpdater, vscode } from "./state/state"; import { distinct } from "../utilities/array"; import { ResourceSelector } from "../components/ResourceSelector"; -import { VSCodeLink } from "@vscode/webview-ui-toolkit/react"; import { faLink, faLinkSlash } from "@fortawesome/free-solid-svg-icons"; import { AcrRoleState } from "./state/stateTypes"; import { InlineAction, InlineActionProps, makeFixAction, makeInlineActionProps } from "../components/InlineAction"; @@ -96,16 +95,16 @@ export function AttachAcrToCluster(initialState: InitialState) {

Select a cluster and Azure Container Registry (ACR) to attach. For more information on attaching an ACR to a cluster, see{" "} - + Configure ACR integration for an existing AKS cluster - + .

This operation assigns the{" "} - + AcrPull - {" "} + {" "} role to the Microsoft Entra ID managed identity associated with your AKS cluster.

diff --git a/webview-ui/src/AutomatedDeployments/AutomatedDeployments.tsx b/webview-ui/src/AutomatedDeployments/AutomatedDeployments.tsx index c03d69ceb..269a1667c 100644 --- a/webview-ui/src/AutomatedDeployments/AutomatedDeployments.tsx +++ b/webview-ui/src/AutomatedDeployments/AutomatedDeployments.tsx @@ -1,6 +1,5 @@ //import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { InitialState } from "../../../src/webview-contract/webviewDefinitions/automatedDeployments"; -//import { VSCodeButton, VSCodeTextField } from "@vscode/webview-ui-toolkit/react"; //import { faFolder } from "@fortawesome/free-regular-svg-icons"; import { @@ -26,16 +25,6 @@ import { valid, } from "../utilities/validation"; import { ResourceSelector } from "../components/ResourceSelector"; -import { - VSCodeButton, - //VSCodeButton, - //VSCodeLink, - //VSCodeRadio, - //VSCodeRadioGroup, - VSCodeTextField, - //VSCodeDropdown, - //VSCodeOption, -} from "@vscode/webview-ui-toolkit/react"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { @@ -152,7 +141,8 @@ export function AutomatedDeployments(initialState: InitialState) { Workflow name * - console.log("Selected Resource Group:", g)} /> - eventHandlers.onSetIsNewResourceGroupDialogShown(true)} > Create New Resource Group - + {state.isNewResourceGroupDialogShown && (
- +
); diff --git a/webview-ui/src/AzureServiceOperator/AzureServiceOperator.tsx b/webview-ui/src/AzureServiceOperator/AzureServiceOperator.tsx index 347a4791d..621650290 100644 --- a/webview-ui/src/AzureServiceOperator/AzureServiceOperator.tsx +++ b/webview-ui/src/AzureServiceOperator/AzureServiceOperator.tsx @@ -1,4 +1,3 @@ -import { VSCodeLink } from "@vscode/webview-ui-toolkit/react"; import styles from "./AzureServiceOperator.module.css"; import { InitialState } from "../../../src/webview-contract/webviewDefinitions/azureServiceOperator"; import { useEffect } from "react"; @@ -64,7 +63,7 @@ export function AzureServiceOperator(initialState: InitialState) {

The Azure Service Operator helps you provision Azure resources and connect your applications to them from within Kubernetes. -  Learn more +  Learn more

diff --git a/webview-ui/src/AzureServiceOperator/Inputs.tsx b/webview-ui/src/AzureServiceOperator/Inputs.tsx index 3a9529e2f..e45bf3f08 100644 --- a/webview-ui/src/AzureServiceOperator/Inputs.tsx +++ b/webview-ui/src/AzureServiceOperator/Inputs.tsx @@ -1,10 +1,3 @@ -import { - VSCodeButton, - VSCodeDropdown, - VSCodeLink, - VSCodeOption, - VSCodeTextField, -} from "@vscode/webview-ui-toolkit/react"; import styles from "./AzureServiceOperator.module.css"; import { ASOState, EventDef, InstallStepStatus } from "./helpers/state"; import { EventHandlers } from "../utilities/state"; @@ -14,6 +7,8 @@ import { MessageSink } from "../../../src/webview-contract/messaging"; import { getRequiredInputs } from "./helpers/inputs"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { faInfoCircle } from "@fortawesome/free-solid-svg-icons"; +import { CustomDropdown } from "../components/CustomDropdown"; +import { CustomDropdownOption } from "../components/CustomDropdownOption"; type ChangeEvent = Event | FormEvent; @@ -45,10 +40,8 @@ export function Inputs(props: InputsProps) { props.handlers.onSetCheckingSP(); } - function handleSubscriptionChanged(e: Event | FormEvent) { - const elem = e.target as HTMLInputElement; - const subscriptionId = elem.value || null; - props.handlers.onSetSelectedSubscriptionId(subscriptionId); + function handleSubscriptionChanged(value: string) { + props.handlers.onSetSelectedSubscriptionId(value); } function handleSubmit(e: FormEvent) { @@ -74,15 +67,16 @@ export function Inputs(props: InputsProps) { Provide the App ID and password of a Service Principal with Contributor permissions for your subscription. This allows ASO to create resources in your subscription on your behalf. - +   Learn more - +

- - - +
{canViewSubscriptions && (
@@ -121,31 +114,28 @@ export function Inputs(props: InputsProps) { The supplied service principal has some role assignments on the following subscriptions. Please ensure these are adequate for the Azure resources that ASO will be creating in your selected subscription. - -   Learn more - +   Learn more

- - {subscriptions.length !== 1 && Select} + {subscriptions.length !== 1 && } {subscriptions.map((s) => ( - - {s.name} - + ))} - - + +
)} diff --git a/webview-ui/src/CreateCluster/CreateCluster.tsx b/webview-ui/src/CreateCluster/CreateCluster.tsx index 539611294..d75265898 100644 --- a/webview-ui/src/CreateCluster/CreateCluster.tsx +++ b/webview-ui/src/CreateCluster/CreateCluster.tsx @@ -38,10 +38,11 @@ export function CreateCluster(initialState: InitialState) { /> ); case Stage.Creating: + console.log("Creating cluster"); return ( <>

- Creating Cluster {state.createParams!.name} in {state.createParams!.location} + Creating Cluster {state.createParams?.name} in {state.createParams?.location}

{state.deploymentPortalUrl && (

@@ -62,7 +63,10 @@ export function CreateCluster(initialState: InitialState) { ); case Stage.Succeeded: return ( - + ); default: throw new Error(`Unexpected stage ${state.stage}`); diff --git a/webview-ui/src/CreateCluster/CreateClusterInput.tsx b/webview-ui/src/CreateCluster/CreateClusterInput.tsx index 7fee7a644..a30492f67 100644 --- a/webview-ui/src/CreateCluster/CreateClusterInput.tsx +++ b/webview-ui/src/CreateCluster/CreateClusterInput.tsx @@ -1,6 +1,6 @@ import { faTimesCircle } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { VSCodeDropdown, VSCodeOption } from "@vscode/webview-ui-toolkit/react"; +// import { VSCodeDropdown, VSCodeOption } from "@vscode/webview-ui-toolkit/react"; import { FormEvent, useState } from "react"; import { MessageSink } from "../../../src/webview-contract/messaging"; import { @@ -16,9 +16,9 @@ import styles from "./CreateCluster.module.css"; import { CreateClusterPresetInput } from "./CreateClusterPresetInput"; import { CreateResourceGroupDialog } from "./CreateResourceGroup"; import { EventDef } from "./helpers/state"; - -type ChangeEvent = Event | FormEvent; - +import { CustomDropdown } from "../components/CustomDropdown"; +import { CustomDropdownOption } from "../components/CustomDropdownOption"; +import { MouseEvent } from "react"; interface CreateClusterInputProps { locations: string[]; resourceGroups: ResourceGroup[]; @@ -32,7 +32,7 @@ export function CreateClusterInput(props: CreateClusterInputProps) { const [isNewResourceGroupDialogShown, setIsNewResourceGroupDialogShown] = useState(false); const [newResourceGroupName, setNewResourceGroupName] = useState(null); const [presetSelected, setPresetSelected] = useState(PresetType.Automatic); - const [selectedIndex, setSelectedIndex] = useState(0); + const [selectedResourceGroup, setSelectedResourceGroup] = useState(""); const [location, setLocation] = useState>(unset()); const newResourceGroup = newResourceGroupName @@ -51,26 +51,30 @@ export function CreateClusterInput(props: CreateClusterInputProps) { setIsNewResourceGroupDialogShown(false); setExistingResourceGroup(valid(null)); setNewResourceGroupName(groupName); - setSelectedIndex(1); // this is the index of the new resource group and the first option is "Select" + setSelectedResourceGroup(groupName); } function handlePresetSelection(presetSelected: PresetType) { setPresetSelected(presetSelected); } - function handleValidationAndIndex(e: ChangeEvent) { - handleExistingResourceGroupChange(e); - const ele = e.currentTarget as HTMLSelectElement; - setSelectedIndex(ele.selectedIndex); + function handleValidationAndIndex(value: string) { + handleExistingResourceGroupChange(value); + setSelectedResourceGroup(value); } - function handleExistingResourceGroupChange(e: ChangeEvent) { - const elem = e.currentTarget as HTMLSelectElement; - const resourceGroup = elem.selectedIndex <= 0 ? null : allResourceGroups[elem.selectedIndex - 1]; + function handleExistingResourceGroupChange(value: string) { + const resourceGroup = value ? allResourceGroups.find((group) => group.name === value) : null; const validatable = resourceGroup ? valid(resourceGroup) : invalid(null, "Resource Group is required."); setExistingResourceGroup(validatable); } + const handleCreateNewRG = (event: MouseEvent) => { + setIsNewResourceGroupDialogShown(true); + event.preventDefault(); + event.stopPropagation(); + }; + function getValidatedName(name: string): Validatable { if (!name) return invalid(name, "Cluster name must be at least 1 character long."); if (name.length > 63) return invalid(name, "Cluster name must be at most 63 characters long."); @@ -84,15 +88,14 @@ export function CreateClusterInput(props: CreateClusterInputProps) { return valid(name); } - function handleNameChange(e: ChangeEvent) { - const name = (e.currentTarget as HTMLInputElement).value; + function handleNameChange(e: FormEvent) { + const name = e.currentTarget.value; const validated = getValidatedName(name); setName(validated); } - function handleLocationChange(e: ChangeEvent) { - const elem = e.currentTarget as HTMLSelectElement; - const location = elem.selectedIndex <= 0 ? null : props.locations[elem.selectedIndex - 1]; + function handleLocationChange(value: string) { + const location = value ? props.locations.find((loc) => loc === value) : null; const validated = location ? valid(location) : missing("Location is required."); setLocation(validated); } @@ -139,32 +142,32 @@ export function CreateClusterInput(props: CreateClusterInputProps) { + - - - Select - + {allResourceGroups.length > 0 ? ( allResourceGroups.map((group) => ( - - {group === newResourceGroup ? "(New)" : ""} {group.name} - + )) ) : ( - No resource groups available + )} - + - {hasMessage(existingResourceGroup) && ( @@ -195,19 +198,17 @@ export function CreateClusterInput(props: CreateClusterInputProps) { - - Select + {props.locations.map((location) => ( - - {location} - + ))} - + {hasMessage(location) && ( diff --git a/webview-ui/src/CreateFleet/CreateFleetInput.tsx b/webview-ui/src/CreateFleet/CreateFleetInput.tsx index c74442bb3..ccd0c587e 100644 --- a/webview-ui/src/CreateFleet/CreateFleetInput.tsx +++ b/webview-ui/src/CreateFleet/CreateFleetInput.tsx @@ -5,7 +5,6 @@ import { ResourceGroup, ToVsCodeMsgDef, } from "../../../src/webview-contract/webviewDefinitions/createFleet"; -import { VSCodeDropdown, VSCodeOption } from "@vscode/webview-ui-toolkit/react"; import { hasMessage, invalid, @@ -25,6 +24,8 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { faTimesCircle } from "@fortawesome/free-solid-svg-icons"; import styles from "./CreateFleet.module.css"; import { CreateFleetModeInput } from "./CreateFleetModeInput"; +import { CustomDropdown } from "../components/CustomDropdown"; +import { CustomDropdownOption } from "../components/CustomDropdownOption"; type ChangeEvent = Event | FormEvent; @@ -39,22 +40,21 @@ interface CreateFleetInputProps { export function CreateFleetInput(props: CreateFleetInputProps) { const [existingResourceGroup, setExistingResourceGroup] = useState>(unset()); const [fleetName, setFleetName] = useState>(unset()); - const [selectedResourceGroupIndex, setselectedResourceGroupIndex] = useState(0); + // const [selectedResourceGroupIndex, setselectedResourceGroupIndex] = useState(0); const [location, setLocation] = useState>(unset()); const [hubModeSelected, setHubModeSelected] = useState(HubMode.With); const [dnsPrefix, setDnsPrefix] = useState>(unset()); + const [selectedResourceGroup, setSelectedResourceGroup] = useState(""); const allResourcesGroups = props.resourceGroups; // All available resource groups fetched from the portal - function handleValidationAndIndex(e: ChangeEvent) { - handleExistingResourceGroupChange(e); - const ele = e.currentTarget as HTMLSelectElement; - setselectedResourceGroupIndex(ele.selectedIndex); + function handleValidationAndIndex(value: string) { + handleExistingResourceGroupChange(value); + setSelectedResourceGroup(value); } - function handleExistingResourceGroupChange(e: ChangeEvent) { - const elem = e.currentTarget as HTMLSelectElement; - const resourceGroup = elem.selectedIndex <= 0 ? null : allResourcesGroups[elem.selectedIndex - 1]; + function handleExistingResourceGroupChange(value: string) { + const resourceGroup = value ? allResourcesGroups.find((group) => group.name === value) : null; const validatable = resourceGroup ? valid(resourceGroup) : invalid(null, "Resource Group is required."); setExistingResourceGroup(validatable); } @@ -80,9 +80,8 @@ export function CreateFleetInput(props: CreateFleetInputProps) { return valid(name); } - function handleLocationChange(e: ChangeEvent) { - const elem = e.currentTarget as HTMLSelectElement; - const location = elem.selectedIndex <= 0 ? null : props.locations[elem.selectedIndex - 1]; + function handleLocationChange(value: string) { + const location = value ? props.locations.find((loc) => loc === value) : null; const validated = location ? valid(location) : missing("Location is required."); setLocation(validated); } @@ -151,27 +150,22 @@ export function CreateFleetInput(props: CreateFleetInputProps) { - - - Select - + {allResourcesGroups.length > 0 ? ( allResourcesGroups.map((group) => ( - - {""} {group.name} - + )) ) : ( - No resource groups available + )} - + {hasMessage(existingResourceGroup) && ( @@ -182,19 +176,17 @@ export function CreateFleetInput(props: CreateFleetInputProps) { - - Select + {props.locations.map((location) => ( - - {location} - + ))} - + {hasMessage(location) && ( diff --git a/webview-ui/src/Detector/Detector.tsx b/webview-ui/src/Detector/Detector.tsx index e2bdce31c..c7f86322a 100644 --- a/webview-ui/src/Detector/Detector.tsx +++ b/webview-ui/src/Detector/Detector.tsx @@ -1,4 +1,3 @@ -import { VSCodeDivider, VSCodeLink } from "@vscode/webview-ui-toolkit/react"; import { InitialState } from "../../../src/webview-contract/webviewDefinitions/detector"; import { SingleDetector } from "./SingleDetector"; import { useStateManagement } from "../utilities/state"; @@ -11,9 +10,8 @@ export function Detector(initialState: InitialState) { <>

{state.name}

{state.description && state.description !== "test" &&

{state.description}

} - To perform more checks on your cluster, visit{" "} - AKS Diagnostics. - + To perform more checks on your cluster, visit AKS Diagnostics. +
{state.detectors.map((detector) => ( ))} diff --git a/webview-ui/src/Draft/Draft.module.css b/webview-ui/src/Draft/Draft.module.css index 6f4396519..a5fd24d61 100644 --- a/webview-ui/src/Draft/Draft.module.css +++ b/webview-ui/src/Draft/Draft.module.css @@ -47,6 +47,10 @@ color: var(--vscode-textLink-foreground); } +.linkColor { + color: var(--vscode-textLink-foreground); +} + .buttonContainer { margin-top: 1rem; margin-bottom: 1rem; @@ -102,3 +106,11 @@ ul.existingFileList > li.removable { .nextStepsContainer h3 { margin-top: 0; } + +.radioLabel { + margin-left: 0.62rem; +} + +.radioLine { + margin-bottom: 0.5rem; +} diff --git a/webview-ui/src/Draft/DraftDeployment/DraftDeployment.tsx b/webview-ui/src/Draft/DraftDeployment/DraftDeployment.tsx index 3a7737020..2616da960 100644 --- a/webview-ui/src/Draft/DraftDeployment/DraftDeployment.tsx +++ b/webview-ui/src/Draft/DraftDeployment/DraftDeployment.tsx @@ -32,13 +32,6 @@ import { Lazy, map as lazyMap } from "../../utilities/lazy"; import { ResourceSelector } from "../../components/ResourceSelector"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { faTimesCircle } from "@fortawesome/free-solid-svg-icons"; -import { - VSCodeButton, - VSCodeLink, - VSCodeRadio, - VSCodeRadioGroup, - VSCodeTextField, -} from "@vscode/webview-ui-toolkit/react"; import { faFolder } from "@fortawesome/free-regular-svg-icons"; import { distinct } from "../../utilities/array"; import { TextWithDropdown } from "../../components/TextWithDropdown"; @@ -231,7 +224,6 @@ export function DraftDeployment(initialState: InitialState) { }); } - const [manifests, helm, kustomize]: DeploymentSpecType[] = ["manifests", "helm", "kustomize"]; const existingFiles = getExistingPaths(state.selectedDeploymentSpecType, state.existingFiles); const acrImageTooltipMessage = @@ -372,7 +364,8 @@ export function DraftDeployment(initialState: InitialState) { )} {state.selectedAcrRepository.value.isNew && ( - Location * -
- +
{hasMessage(state.selectedLocation) && ( @@ -460,22 +454,45 @@ export function DraftDeployment(initialState: InitialState) { - - Manifests - Helm - Kustomize - +
+
+ + + +
+
+ + +
+
+ + +
+
-
- +
{existingFiles.length > 0 && ( @@ -565,7 +582,7 @@ export function DraftDeployment(initialState: InitialState) {
    {existingFiles.map((path, i) => (
  • - { e.preventDefault(); @@ -573,7 +590,7 @@ export function DraftDeployment(initialState: InitialState) { }} > {path} - +
  • ))}
@@ -588,9 +605,9 @@ export function DraftDeployment(initialState: InitialState) {

To generate a GitHub Action, you can run{" "} - + Automated Deployments: Create a GitHub workflow - + .

diff --git a/webview-ui/src/Draft/DraftDockerfile/DraftDockerfile.tsx b/webview-ui/src/Draft/DraftDockerfile/DraftDockerfile.tsx index 100cc808b..dec15fc5b 100644 --- a/webview-ui/src/Draft/DraftDockerfile/DraftDockerfile.tsx +++ b/webview-ui/src/Draft/DraftDockerfile/DraftDockerfile.tsx @@ -4,7 +4,6 @@ import { ResourceSelector } from "../../components/ResourceSelector"; import { useStateManagement } from "../../utilities/state"; import styles from "../Draft.module.css"; import { stateUpdater, vscode } from "./state"; -import { VSCodeButton, VSCodeLink, VSCodeTextField } from "@vscode/webview-ui-toolkit/react"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { faFolder } from "@fortawesome/free-regular-svg-icons"; import { faTimesCircle } from "@fortawesome/free-solid-svg-icons"; @@ -146,19 +145,20 @@ export function DraftDockerfile(initialState: InitialState) { -
- +
{hasMessage(state.selectedLocation) && ( @@ -243,15 +243,15 @@ export function DraftDockerfile(initialState: InitialState) {
{state.status !== "Created" && ( - + )} {state.existingFiles.map((path, i) => ( - vscode.postOpenFileRequest(path)}> + ))}
@@ -263,18 +263,18 @@ export function DraftDockerfile(initialState: InitialState) {

If you still need to generate the appropriate deployment files, you can run{" "} - + Automated Deployments: Create a deployment - {" "} + {" "} to easily create the appropriate files.

If you already have all the files you need to deploy and would like to generate a GitHub Action, you can run{" "} - + Automated Deployments: Create a GitHub workflow - + .

diff --git a/webview-ui/src/Draft/DraftWorkflow/DraftWorkflow.tsx b/webview-ui/src/Draft/DraftWorkflow/DraftWorkflow.tsx index aa13f33d1..4744b846d 100644 --- a/webview-ui/src/Draft/DraftWorkflow/DraftWorkflow.tsx +++ b/webview-ui/src/Draft/DraftWorkflow/DraftWorkflow.tsx @@ -40,13 +40,6 @@ import { useStateManagement } from "../../utilities/state"; import styles from "../Draft.module.css"; import { Maybe, isNothing, just, nothing } from "../../utilities/maybe"; import { ResourceSelector } from "../../components/ResourceSelector"; -import { - VSCodeButton, - VSCodeLink, - VSCodeRadio, - VSCodeRadioGroup, - VSCodeTextField, -} from "@vscode/webview-ui-toolkit/react"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { faPlus, faTimesCircle, faTrash } from "@fortawesome/free-solid-svg-icons"; import { faFolder } from "@fortawesome/free-regular-svg-icons"; @@ -385,7 +378,6 @@ export function DraftWorkflow(initialState: InitialState) { vscode.postCreateWorkflowRequest(createParams.value); } - const [manifests, helm]: DeploymentSpecType[] = ["manifests", "helm"]; const existingFile = getExistingFile(state, state.selectedWorkflowName); const gitHubRepoTooltipMessage = @@ -401,13 +393,13 @@ export function DraftWorkflow(initialState: InitialState) {

Generate a workflow to deploy to Azure Kubernetes Service (AKS). Before running this command, make sure you have created a Dockerfile and Deployment. You can do this using the{" "} - + Automated Deployments: Create a Dockerfile - {" "} + {" "} and{" "} - + Automated Deployments: Create a Deployment - {" "} + {" "} commands.

@@ -416,7 +408,8 @@ export function DraftWorkflow(initialState: InitialState) { - Dockerfile * -
- +
{hasMessage(state.selectedDockerfilePath) && ( @@ -521,19 +515,20 @@ export function DraftWorkflow(initialState: InitialState) { -
- +
{isValid(state.selectedSubscription) && ( @@ -685,16 +680,30 @@ export function DraftWorkflow(initialState: InitialState) { - - Manifests - Helm - + +
+
+ + + +
+
+ + +
+
{state.selectedDeploymentSpecType === "manifests" && ( <> @@ -702,18 +711,18 @@ export function DraftWorkflow(initialState: InitialState) { Manifest file paths *
- +
{isValid(state.manifestsParamsState.selectedManifestPaths) && (
    {state.manifestsParamsState.selectedManifestPaths.value.map((path, i) => (
  • - { e.preventDefault(); @@ -721,17 +730,15 @@ export function DraftWorkflow(initialState: InitialState) { }} > {path} - - +
  • ))}
@@ -750,7 +757,8 @@ export function DraftWorkflow(initialState: InitialState) { -
- +
{hasMessage(state.helmParamsState.selectedChartPath) && ( @@ -778,7 +786,8 @@ export function DraftWorkflow(initialState: InitialState) { -
- +
{hasMessage(state.helmParamsState.selectedValuesYamlPath) && ( @@ -807,7 +816,8 @@ export function DraftWorkflow(initialState: InitialState) { {state.helmParamsState.selectedOverrides.map((o, i) => ( <>
- handleOverrideKeyChange(e, o)} @@ -815,23 +825,22 @@ export function DraftWorkflow(initialState: InitialState) { value={orDefault(o.key, "")} /> = - handleOverrideValueChange(e, o)} onInput={(e) => handleOverrideValueChange(e, o)} value={orDefault(o.value, "")} /> - handleDeleteOverrideClick(o)} aria-label="Remove" title="Remove" > - - - - + +
{hasMessage(o.key) && ( @@ -854,12 +863,12 @@ export function DraftWorkflow(initialState: InitialState) { : styles.controlSupplement } > - + )} @@ -867,15 +876,15 @@ export function DraftWorkflow(initialState: InitialState) {
{state.status !== "Created" && ( - + )} {existingFile && ( - vscode.postOpenFileRequest(existingFile)}> + )}
@@ -890,9 +899,9 @@ export function DraftWorkflow(initialState: InitialState) {
  • The ACR {isValueSet(state.selectedAcr) ? `(${state.selectedAcr.value})` : ""}{" "} - + is attached - {" "} + {" "} to the cluster{" "} {isValueSet(state.selectedCluster) ? `(${state.selectedCluster.value})` : ""}. You can follow for guidance. @@ -902,9 +911,9 @@ export function DraftWorkflow(initialState: InitialState) { {isValueSet(state.selectedGitHubRepo) ? `(${state.selectedGitHubRepo.value.gitHubRepoOwner}/${state.selectedGitHubRepo.value.gitHubRepoName})` : ""}{" "} - + is configured - {" "} + {" "} to access the ACR and cluster.
diff --git a/webview-ui/src/InspektorGadget/GadgetSelector.tsx b/webview-ui/src/InspektorGadget/GadgetSelector.tsx index 18f5dfa2e..a84e22b88 100644 --- a/webview-ui/src/InspektorGadget/GadgetSelector.tsx +++ b/webview-ui/src/InspektorGadget/GadgetSelector.tsx @@ -1,8 +1,8 @@ -import { VSCodeDropdown, VSCodeOption } from "@vscode/webview-ui-toolkit/react"; -import { FormEvent } from "react"; import { GadgetCategory } from "./helpers/gadgets/types"; import { configuredGadgetResources } from "./helpers/gadgets"; - +import { CustomDropdown } from "../components/CustomDropdown"; +import { CustomDropdownOption } from "../components/CustomDropdownOption"; +import { useState } from "react"; export interface GadgetSelectorProps { category: GadgetCategory; id: string; @@ -12,27 +12,21 @@ export interface GadgetSelectorProps { } export function GadgetSelector(props: GadgetSelectorProps) { - function handleResourceChange(e: Event | FormEvent) { - const elem = e.target as HTMLInputElement; - const resource = elem.value ? elem.value : null; + function handleResourceChange(value: string) { + const resource = value ? value : null; + setSelectedNode(value); props.onResourceChanged(resource); } const configuredResources = configuredGadgetResources[props.category]; + const [selectedNode, setSelectedNode] = useState(""); return ( - - Select + + {Object.keys(configuredResources).map((resource) => ( - - {configuredResources[resource]!.name} - + ))} - + ); } diff --git a/webview-ui/src/InspektorGadget/InspektorGadget.module.css b/webview-ui/src/InspektorGadget/InspektorGadget.module.css index 10671784b..00b729b8d 100644 --- a/webview-ui/src/InspektorGadget/InspektorGadget.module.css +++ b/webview-ui/src/InspektorGadget/InspektorGadget.module.css @@ -33,6 +33,7 @@ table.tracelist th { table.tracelist td { padding: 5px; + text-align: left; } table.tracelist td > * { @@ -127,3 +128,31 @@ ul.hierarchyList li * { .panelContent .main { grid-column: 2 / 3; } + +.displayCheckbox { + display: inline; +} + +.displayLabel { + display: inline; + position: relative; + top: -0.3rem; + margin-left: 0.1rem; +} + +.tableData { + text-align: left; +} + +.checkBoxLabel { + position: relative; + top: -0.05rem; +} + +.radioLabel { + margin-left: 0.62rem; +} + +.radioLine { + margin-bottom: 0.3rem; +} diff --git a/webview-ui/src/InspektorGadget/InspektorGadget.tsx b/webview-ui/src/InspektorGadget/InspektorGadget.tsx index 4a95cdd4d..9ef7a2087 100644 --- a/webview-ui/src/InspektorGadget/InspektorGadget.tsx +++ b/webview-ui/src/InspektorGadget/InspektorGadget.tsx @@ -1,4 +1,4 @@ -import { VSCodeLink, VSCodePanelTab, VSCodePanelView, VSCodePanels } from "@vscode/webview-ui-toolkit/react"; +import { VSCodePanelTab, VSCodePanelView, VSCodePanels } from "@vscode/webview-ui-toolkit/react"; import { Overview } from "./Overview"; import { Traces, TracesProps } from "./Traces"; import styles from "./InspektorGadget.module.css"; @@ -53,7 +53,7 @@ export function InspektorGadget(initialState: InitialState) {

Inspektor Gadget

Inspektor Gadget provides a wide selection of BPF tools to dig deep into your Kubernetes cluster. -  Learn more +  Learn more

diff --git a/webview-ui/src/InspektorGadget/NewTraceDialog.tsx b/webview-ui/src/InspektorGadget/NewTraceDialog.tsx index 0a212c4d7..27c3d8232 100644 --- a/webview-ui/src/InspektorGadget/NewTraceDialog.tsx +++ b/webview-ui/src/InspektorGadget/NewTraceDialog.tsx @@ -1,4 +1,4 @@ -import { VSCodeButton, VSCodeCheckbox, VSCodeDivider, VSCodeProgressRing } from "@vscode/webview-ui-toolkit/react"; +import { VSCodeProgressRing } from "@vscode/webview-ui-toolkit/react"; import styles from "./InspektorGadget.module.css"; import { Dialog } from "../components/Dialog"; import { FormEvent, useEffect, useState, ChangeEvent as InputChangeEvent } from "react"; @@ -146,7 +146,7 @@ export function NewTraceDialog(props: NewTraceDialogProps) {

New Trace

- +
- +
- + +
diff --git a/webview-ui/src/InspektorGadget/Overview.tsx b/webview-ui/src/InspektorGadget/Overview.tsx index 4cd430b9b..915c2fffe 100644 --- a/webview-ui/src/InspektorGadget/Overview.tsx +++ b/webview-ui/src/InspektorGadget/Overview.tsx @@ -1,4 +1,3 @@ -import { VSCodeButton } from "@vscode/webview-ui-toolkit/react"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { faEraser, faRocket } from "@fortawesome/free-solid-svg-icons"; import styles from "./InspektorGadget.module.css"; @@ -35,17 +34,17 @@ export function Overview(props: OverviewProps) {
{props.version.server}

- + )} {props.version && !props.version.server && ( - + )} ); diff --git a/webview-ui/src/InspektorGadget/ResourceSelector.tsx b/webview-ui/src/InspektorGadget/ResourceSelector.tsx index af36fbc7a..7de74cea3 100644 --- a/webview-ui/src/InspektorGadget/ResourceSelector.tsx +++ b/webview-ui/src/InspektorGadget/ResourceSelector.tsx @@ -4,7 +4,7 @@ import { faChevronDown, faChevronRight } from "@fortawesome/free-solid-svg-icons import styles from "./InspektorGadget.module.css"; import { NamespaceResources, PodResources } from "./helpers/clusterResources"; import { Lazy, isLoaded, isNotLoaded, map as lazyMap, orDefault } from "../utilities/lazy"; -import { VSCodeProgressRing, VSCodeRadio } from "@vscode/webview-ui-toolkit/react"; +import { VSCodeProgressRing } from "@vscode/webview-ui-toolkit/react"; import { Lookup, asLookup, exclude, intersection } from "../utilities/array"; import { EventHandlers } from "../utilities/state"; import { EventDef, vscode } from "./helpers/state"; @@ -109,9 +109,14 @@ export function ResourceSelector(props: ResourceSelectorProps) { className={props.className ? `${props.className} ${styles.hierarchyList}` : styles.hierarchyList} >
  • - - All - +
    + + +
  • {renderNamespaceItems(props.resources, updatedStatus)} @@ -125,13 +130,15 @@ export function ResourceSelector(props: ResourceSelectorProps) { onClick={() => toggleNamespaceExpanded(item.name)} icon={status[item.name].isExpanded ? faChevronDown : faChevronRight} /> - handleNamespaceChange(e, item.name)} - checked={isNamespaceResource(selectedResource) && selectedResource.namespace === item.name} - > - {item.name} - +
    + handleNamespaceChange(e, item.name)} + checked={isNamespaceResource(selectedResource) && selectedResource.namespace === item.name} + > + +
    {status[item.name].isExpanded && (
      {isLoaded(item.children) ? ( @@ -153,16 +160,19 @@ export function ResourceSelector(props: ResourceSelectorProps) { onClick={() => togglePodExpanded(namespace, item.name)} icon={status[item.name].isExpanded ? faChevronDown : faChevronRight} /> - handlePodChange(e, namespace, item.name)} - checked={ - isPodResource(selectedResource) && - selectedResource.namespace === namespace && - selectedResource.podName === item.name - } - > - {item.name} - +
      + handlePodChange(e, namespace, item.name)} + checked={ + isPodResource(selectedResource) && + selectedResource.namespace === namespace && + selectedResource.podName === item.name + } + > + + +
      {status[item.name].isExpanded && (
        {isLoaded(item.children) ? ( @@ -179,17 +189,19 @@ export function ResourceSelector(props: ResourceSelectorProps) { function renderContainerItems(namespace: string, podName: string, containerNames: string[]) { return containerNames.map((c) => (
      • - handleContainerChange(e, namespace, podName, c)} - checked={ - isContainerResource(selectedResource) && - selectedResource.namespace === namespace && - selectedResource.podName === podName && - selectedResource.container === c - } - > - {c} - +
        + handleContainerChange(e, namespace, podName, c)} + checked={ + isContainerResource(selectedResource) && + selectedResource.namespace === namespace && + selectedResource.podName === podName && + selectedResource.container === c + } + > + +
      • )); } diff --git a/webview-ui/src/InspektorGadget/TraceItemSortSelector.tsx b/webview-ui/src/InspektorGadget/TraceItemSortSelector.tsx index 4afc954d9..c2a7ba943 100644 --- a/webview-ui/src/InspektorGadget/TraceItemSortSelector.tsx +++ b/webview-ui/src/InspektorGadget/TraceItemSortSelector.tsx @@ -1,4 +1,3 @@ -import { VSCodeTextField } from "@vscode/webview-ui-toolkit/react"; import { FormEvent } from "react"; import { ItemProperty, SortSpecifier, fromSortString, toSortString } from "./helpers/gadgets/types"; @@ -24,13 +23,14 @@ export function TraceItemSortSelector(props: TraceItemSortSelectorProps) { const allowedIdentifiers = props.allProperties.map((p) => p.identifier).sort(); const title = `Allowed properties:\n${allowedIdentifiers.join("\n")}`; return ( - + > ); } diff --git a/webview-ui/src/InspektorGadget/Traces.tsx b/webview-ui/src/InspektorGadget/Traces.tsx index aad1ad84f..083dd71bd 100644 --- a/webview-ui/src/InspektorGadget/Traces.tsx +++ b/webview-ui/src/InspektorGadget/Traces.tsx @@ -1,4 +1,3 @@ -import { VSCodeButton, VSCodeCheckbox, VSCodeDivider } from "@vscode/webview-ui-toolkit/react"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { faPlus, faTrashCan, faEye, faEyeSlash } from "@fortawesome/free-solid-svg-icons"; import styles from "./InspektorGadget.module.css"; @@ -35,7 +34,6 @@ export function Traces(props: TracesProps) { } function ignoreClick(e: Event | FormEvent) { - e.preventDefault(); e.stopPropagation(); } @@ -140,13 +138,15 @@ export function Traces(props: TracesProps) { className={getTraceRowClassNames(trace.traceId)} > - toggleCheckedTraceId(trace.traceId)} - style={{ margin: "0", paddingRight: "0.5rem" }} + style={{ margin: "0 0.5rem 0 0" }} /> - {getGadgetMetadata(trace.category, trace.resource)?.name} + + {getGadgetMetadata(trace.category, trace.resource)?.name} + {getNamespaceText(trace.filters.namespace)} {trace.filters.nodeName} @@ -159,19 +159,19 @@ export function Traces(props: TracesProps) { )}
        - + {checkedTraceIds.length > 0 && ( - + )}
        - +
        {selectedTrace && ( <> diff --git a/webview-ui/src/KaitoModels/KaitoModels.module.css b/webview-ui/src/KaitoModels/KaitoModels.module.css index 0871c4a9e..7dbbe04cf 100644 --- a/webview-ui/src/KaitoModels/KaitoModels.module.css +++ b/webview-ui/src/KaitoModels/KaitoModels.module.css @@ -415,4 +415,5 @@ .closeButton:hover { color: #ff6b6b; + background: none; } diff --git a/webview-ui/src/Kubectl/CommandInput.tsx b/webview-ui/src/Kubectl/CommandInput.tsx index a85e5de4b..d1b10541e 100644 --- a/webview-ui/src/Kubectl/CommandInput.tsx +++ b/webview-ui/src/Kubectl/CommandInput.tsx @@ -1,4 +1,3 @@ -import { VSCodeButton, VSCodeTextField } from "@vscode/webview-ui-toolkit/react"; import styles from "./Kubectl.module.css"; import { FormEvent } from "react"; @@ -32,7 +31,8 @@ export function CommandInput(props: CommandInputProps) { -
        - props.onRunCommand(props.command)}> + + {!props.matchesExisting && }
        ); diff --git a/webview-ui/src/Kubectl/Kubectl.tsx b/webview-ui/src/Kubectl/Kubectl.tsx index e2a81e586..847ddb5c2 100644 --- a/webview-ui/src/Kubectl/Kubectl.tsx +++ b/webview-ui/src/Kubectl/Kubectl.tsx @@ -1,4 +1,3 @@ -import { VSCodeDivider } from "@vscode/webview-ui-toolkit/react"; import { CommandCategory, InitialState, PresetCommand } from "../../../src/webview-contract/webviewDefinitions/kubectl"; import styles from "./Kubectl.module.css"; import { CommandList } from "./CommandList"; @@ -75,7 +74,7 @@ export function Kubectl(initialState: InitialState) {

        Kubectl Command Run for {state.clusterName}

        - +
        - Yes - No + +
        diff --git a/webview-ui/src/RetinaCapture/RetinaCapture.tsx b/webview-ui/src/RetinaCapture/RetinaCapture.tsx index 0648365e5..208d408fc 100644 --- a/webview-ui/src/RetinaCapture/RetinaCapture.tsx +++ b/webview-ui/src/RetinaCapture/RetinaCapture.tsx @@ -1,6 +1,5 @@ import { faInfoCircle, faTrash } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { VSCodeButton, VSCodeCheckbox, VSCodeDivider } from "@vscode/webview-ui-toolkit/react"; import { FormEvent, useState } from "react"; import { InitialState } from "../../../src/webview-contract/webviewDefinitions/retinaCapture"; import { useStateManagement } from "../utilities/state"; @@ -43,44 +42,52 @@ export function RetinaCapture(initialState: InitialState) {

        Retina Distributed Capture for {state.clusterName}

        - +
        Retina capture command allows the user to capture network traffic and metadata for the capture target, and then send the capture file to the location by Output Configuration. More info:{" "} Retina Capture Command
        - +

        Retina Output

        {state.retinaOutput}
        - +

        Retina Distributed Capture is Successfully Completed for this Cluster

        {state.allNodes.map((node) => (
        - onSelectNode(e, node)} checked={isNodeSelected(node)}> - {node} - + onSelectNode(e, node)} + checked={isNodeSelected(node)} + type="checkbox" + style={{ + margin: "0rem 0.5rem 0.5rem 0", + position: "relative", + top: ".125rem", + }} + /> + {node}
        ))} -
        - + {state.isNodeExplorerPodExists && ( - handleDeleteExplorerPod()}> - + <> + + )}
        diff --git a/webview-ui/src/TCPDump/CaptureFilters.tsx b/webview-ui/src/TCPDump/CaptureFilters.tsx index 8f25c27c9..3998b0aab 100644 --- a/webview-ui/src/TCPDump/CaptureFilters.tsx +++ b/webview-ui/src/TCPDump/CaptureFilters.tsx @@ -8,7 +8,6 @@ import { SpecificPodFilters } from "./filterScenarios/SpecificPodFilters"; import { TwoPodsFilters } from "./filterScenarios/TwoPodsFilters"; import { CaptureScenario, EventDef, NodeState, ReferenceData } from "./state"; import { EventHandlerFunc, loadCaptureInterfaces } from "./state/dataLoading"; -import { VSCodeTextField } from "@vscode/webview-ui-toolkit/react"; import { ApplicationLayerProtocol, TransportLayerProtocol, @@ -178,9 +177,10 @@ export function CaptureFilters(props: CaptureFiltersProps) { - diff --git a/webview-ui/src/TCPDump/TcpDump.module.css b/webview-ui/src/TCPDump/TcpDump.module.css index c4fe2d47c..42424224f 100644 --- a/webview-ui/src/TCPDump/TcpDump.module.css +++ b/webview-ui/src/TCPDump/TcpDump.module.css @@ -62,4 +62,13 @@ table.capturelist th { table.capturelist td { padding: 5px; + text-align: left; +} + +button span { + margin-right: 0.5rem; +} + +.pcapFilterInput { + width: 27rem; } diff --git a/webview-ui/src/TCPDump/TcpDump.tsx b/webview-ui/src/TCPDump/TcpDump.tsx index c12e0c5b6..ff2276d27 100644 --- a/webview-ui/src/TCPDump/TcpDump.tsx +++ b/webview-ui/src/TCPDump/TcpDump.tsx @@ -1,7 +1,7 @@ import { CaptureName, InitialState } from "../../../src/webview-contract/webviewDefinitions/tcpDump"; import styles from "./TcpDump.module.css"; import { useEffect } from "react"; -import { VSCodeButton, VSCodeDivider, VSCodeProgressRing } from "@vscode/webview-ui-toolkit/react"; +import { VSCodeProgressRing } from "@vscode/webview-ui-toolkit/react"; import { useStateManagement } from "../utilities/state"; import { CaptureStatus, NodeState, NodeStatus, TcpDumpState, stateUpdater, vscode } from "./state"; import { NodeSelector } from "../components/NodeSelector"; @@ -93,7 +93,7 @@ export function TcpDump(initialState: InitialState) {

        TCP Capture on {state.clusterName}

        - +
        - +

        Completed Captures

        {hasStatus( NodeStatus.DebugPodRunning, @@ -239,10 +238,10 @@ export function TcpDump(initialState: InitialState) { {c.sizeInKB} {!c.downloadedFilePath && ( - handleStartDownload(c.name)} disabled={c.status !== CaptureStatus.Completed} - appearance="secondary" + className="secondary-button" > {c.status === CaptureStatus.Downloading && ( @@ -255,27 +254,27 @@ export function TcpDump(initialState: InitialState) { )} Download - + )} {c.downloadedFilePath && (
        {c.downloadedFilePath}   - + +
        )} diff --git a/webview-ui/src/TCPDump/filterScenarios/SpecificPodFilters.tsx b/webview-ui/src/TCPDump/filterScenarios/SpecificPodFilters.tsx index 7ca7da1cd..90426f7b7 100644 --- a/webview-ui/src/TCPDump/filterScenarios/SpecificPodFilters.tsx +++ b/webview-ui/src/TCPDump/filterScenarios/SpecificPodFilters.tsx @@ -5,9 +5,10 @@ import { Lazy, isLoaded, newLoading } from "../../utilities/lazy"; import styles from "../TcpDump.module.css"; import { FilterPod, NodeName } from "../../../../src/webview-contract/webviewDefinitions/tcpDump"; import { EventHandlerFunc, loadAllNodes, loadFilterPods } from "../state/dataLoading"; -import { FormEvent, useEffect } from "react"; +import { useEffect } from "react"; import { getOrThrow } from "../../utilities/array"; -import { VSCodeDropdown, VSCodeOption } from "@vscode/webview-ui-toolkit/react"; +import { CustomDropdown } from "../../components/CustomDropdown"; +import { CustomDropdownOption } from "../../components/CustomDropdownOption"; export interface SpecificPodFiltersProps { captureNode: NodeName; @@ -32,9 +33,9 @@ export function SpecificPodFilters(props: SpecificPodFiltersProps) { props.eventHandlers.onRefreshPcapFilterString({ node: props.captureNode }); } - function handlePacketDirectionChange(e: Event | FormEvent) { - const elem = e.target as HTMLInputElement; - const packetDirection = elem.value as SingleEndpointPacketDirection; + function handlePacketDirectionChange(value: string) { + // const elem = e.target as HTMLInputElement; + const packetDirection = value as SingleEndpointPacketDirection; props.eventHandlers.onSetCaptureScenarioFilters({ node: props.captureNode, scenario: "SpecificPod", @@ -67,18 +68,20 @@ export function SpecificPodFilters(props: SpecificPodFiltersProps) { - {Object.keys(packetDirectionLabels).map((d) => ( - - {packetDirectionLabels[d as SingleEndpointPacketDirection]} - + ))} - + ); } diff --git a/webview-ui/src/TCPDump/filterScenarios/TwoPodsFilters.tsx b/webview-ui/src/TCPDump/filterScenarios/TwoPodsFilters.tsx index 70456cc23..cb8b937b7 100644 --- a/webview-ui/src/TCPDump/filterScenarios/TwoPodsFilters.tsx +++ b/webview-ui/src/TCPDump/filterScenarios/TwoPodsFilters.tsx @@ -6,8 +6,9 @@ import styles from "../TcpDump.module.css"; import { FilterPod, NodeName } from "../../../../src/webview-contract/webviewDefinitions/tcpDump"; import { EventHandlerFunc, loadAllNodes, loadFilterPods } from "../state/dataLoading"; import { getOrThrow } from "../../utilities/array"; -import { FormEvent, useEffect } from "react"; -import { VSCodeDropdown, VSCodeOption } from "@vscode/webview-ui-toolkit/react"; +import { useEffect } from "react"; +import { CustomDropdown } from "../../components/CustomDropdown"; +import { CustomDropdownOption } from "../../components/CustomDropdownOption"; export interface TwoPodsFiltersProps { captureNode: NodeName; @@ -64,9 +65,9 @@ export function TwoPodsFilters(props: TwoPodsFiltersProps) { props.eventHandlers.onRefreshPcapFilterString({ node: props.captureNode }); } - function handlePacketDirectionChange(e: Event | FormEvent) { - const elem = e.target as HTMLInputElement; - const packetDirection = elem.value as DualEndpointPacketDirection; + function handlePacketDirectionChange(value: string) { + // const elem = e.target as HTMLInputElement; + const packetDirection = value as DualEndpointPacketDirection; props.eventHandlers.onSetCaptureScenarioFilters({ node: props.captureNode, scenario: "TwoPods", @@ -137,18 +138,20 @@ export function TwoPodsFilters(props: TwoPodsFiltersProps) { - {Object.keys(packetDirectionLabels).map((d) => ( - - {packetDirectionLabels[d as DualEndpointPacketDirection]} - + ))} - + ); } diff --git a/webview-ui/src/components/CustomDropdown.module.css b/webview-ui/src/components/CustomDropdown.module.css new file mode 100644 index 000000000..62f2adaae --- /dev/null +++ b/webview-ui/src/components/CustomDropdown.module.css @@ -0,0 +1,95 @@ +.control { + width: 100%; + padding: 8px; + font-size: 14px; +} + +.dropdown { + position: relative; + display: inline-block; + width: 100%; +} + +.dropdownButton { + width: 100%; + cursor: pointer; + text-align: left; + height: calc(var(--input-height) * 1px); + color: var(--input-foreground); + background-color: var(--input-background); + padding: 0px 2.2rem 0px 9px; + box-sizing: border-box; + border-radius: 2px; + font-family: inherit; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + border: 1px solid var(--vscode-dropdown-border); +} + +.dropdownButton:hover { + background-color: var(--input-background); + border: 1px solid var(--vscode-dropdown-border); +} + +.dropdownButton:disabled { + cursor: not-allowed; + background-color: var(--input-background); +} + +.dropdownButton:focus { + border: 1px solid var(--vscode-focusBorder); +} + +.dropdownMenu { + position: absolute; + width: 100%; + background-color: var(--input-background); + box-shadow: 0 8px 16px rgba(0, 0, 0, 0.2); + z-index: 1; + max-height: 10rem; + overflow-y: auto; + overflow-x: hidden; + margin: 0; + padding: 0; + list-style: none; + border: 1px solid var(--vscode-focusBorder); + border-radius: 2px; +} + +.dropUp { + bottom: 100%; + top: auto; + box-shadow: 0 -8px 16px rgba(0, 0, 0, 0.2); + border-radius: 2px 2px 2px 2px; +} + +.dropdownOption { + cursor: pointer; + background-color: var(--input-background); + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + padding-left: 9px; + padding: 2px 5px 2px 5px; +} + +.dropdownOption:hover { + background-color: var(--vscode-list-hoverBackground); +} + +.dropdownOption:active { + background-color: var(--vscode-list-activeSelectionBackground); +} + +.highlightedOption { + background-color: var(--vscode-list-hoverBackground); +} + +.arrowIcon { + position: absolute; + right: 8px; + top: 50%; + transform: translateY(-50%); + pointer-events: none; +} diff --git a/webview-ui/src/components/CustomDropdown.tsx b/webview-ui/src/components/CustomDropdown.tsx new file mode 100644 index 000000000..0daeae728 --- /dev/null +++ b/webview-ui/src/components/CustomDropdown.tsx @@ -0,0 +1,242 @@ +import React, { + useState, + useEffect, + useRef, + ReactNode, + ReactElement, + KeyboardEvent, + MouseEvent as ReactMouseEvent, +} from "react"; +import styles from "./CustomDropdown.module.css"; + +interface CustomDropdownProps { + id?: string; + value?: string; + disabled?: boolean; + onChange: (value: string) => void; + children?: ReactNode; + className?: string; +} + +function isReactElement(child: ReactNode): child is ReactElement<{ + value: string; + label: string; + onClick?: (value: string) => void; + id?: string; + className?: string; +}> { + return React.isValidElement(child); +} +// eslint-disable-next-line @typescript-eslint/naming-convention +export const CustomDropdown: React.FC = ({ + id, + value, + disabled, + onChange, + children, + className, +}) => { + const [isOpen, setIsOpen] = useState(false); + const [dropUp, setDropUp] = useState(false); + const [highlightedIndex, setHighlightedIndex] = useState(0); + // tracks the currently typed search term + const [searchTerm, setSearchTerm] = useState(""); + const searchTimeout = useRef(undefined); + const dropdownRef = useRef(null); + const menuRef = useRef(null); + + // handle toggling of dropdown menu + const handleToggle = (event: ReactMouseEvent) => { + // prevents conflicting default behaviors + event.preventDefault(); + event.stopPropagation(); + if (!disabled) { + const childrenArray = React.Children.toArray(children).filter(isReactElement); + const selectedIndex = childrenArray.findIndex((child) => child.props.value === value); + setHighlightedIndex(selectedIndex !== -1 ? selectedIndex : 0); + setIsOpen(!isOpen); + } + }; + + // handle selection of dropdown option + const handleOptionClick = (optionValue: string) => { + onChange(optionValue); + setIsOpen(false); + }; + + // handle all other clicks outside the dropdown + const handleClickOutside = (event: MouseEvent) => { + if (dropdownRef.current && !dropdownRef.current.contains(event.target as Node)) { + setIsOpen(false); + } + }; + + // Cleanup the search timeout on unmount + useEffect(() => { + return () => { + if (searchTimeout.current) { + clearTimeout(searchTimeout.current); + } + }; + }, []); + + // handling all key inputs + const handleKeyDown = (event: KeyboardEvent) => { + // if key is alphanumeric, update the search term + if (event.key.length === 1 && /^[a-z0-9]$/i.test(event.key)) { + const newSearchTerm = searchTerm + event.key.toLowerCase(); + setSearchTerm(newSearchTerm); + + // if a search timeout is already set, clear it + if (searchTimeout.current) { + clearTimeout(searchTimeout.current); + } + // this gives the user 500ms to type the next character + searchTimeout.current = window.setTimeout(() => { + setSearchTerm(""); + }, 500); + + const childrenArray = React.Children.toArray(children).filter(isReactElement); + // matching for the first element that starts with the search term + const matchIndex = childrenArray.findIndex((child) => + child.props.label.toLowerCase().startsWith(newSearchTerm), + ); + + // if match is found, update the highlighted index + if (matchIndex !== -1) { + if (isOpen) { + setHighlightedIndex(matchIndex); + // scrolls the matched element into view + menuRef.current?.children[matchIndex]?.scrollIntoView({ block: "nearest" }); + } else { + // If dropdown is closed, update the selected value immediately + onChange(childrenArray[matchIndex].props.value); + } + event.preventDefault(); + } + return; + } + + // if dropdown is closed, open it and prevent default behavior + if (!isOpen) { + if (event.key === "ArrowDown" || event.key === "ArrowUp") { + setIsOpen(true); + event.preventDefault(); + } + return; + } + + // handling key inputs on open dropdown menu + switch (event.key) { + case "ArrowDown": + setHighlightedIndex((prevIndex) => { + const newIndex = (prevIndex + 1) % React.Children.count(children); + menuRef.current?.children[newIndex]?.scrollIntoView({ block: "nearest" }); + return newIndex; + }); + event.preventDefault(); + break; + case "ArrowUp": + setHighlightedIndex((prevIndex) => { + const count = React.Children.count(children); + const newIndex = (prevIndex - 1 + count) % count; + menuRef.current?.children[newIndex]?.scrollIntoView({ block: "nearest" }); + return newIndex; + }); + event.preventDefault(); + break; + case "Enter": { + const childrenArray = React.Children.toArray(children).filter(isReactElement); + const selectedChild = childrenArray[highlightedIndex] as ReactElement<{ + value: string; + }>; + handleOptionClick(selectedChild.props.value); + event.preventDefault(); + break; + } + case "Escape": + setIsOpen(false); + event.preventDefault(); + break; + } + }; + + // handle clicks outside the dropdown + useEffect(() => { + document.addEventListener("mousedown", handleClickOutside as EventListener); + return () => { + document.removeEventListener("mousedown", handleClickOutside as EventListener); + }; + }, []); + + // handle dropdown menu positioning + useEffect(() => { + if (isOpen && dropdownRef.current && menuRef.current) { + const dropdownRect = dropdownRef.current.getBoundingClientRect(); + const menuHeight = menuRef.current.offsetHeight; + const viewportHeight = window.innerHeight; + + if (dropdownRect.bottom + menuHeight > viewportHeight) { + setDropUp(true); + } else { + setDropUp(false); + } + } + }, [isOpen]); + + const childrenArray = React.Children.toArray(children).filter(isReactElement); + // updates visible button label based on highlighted index + const buttonLabel = + isOpen && childrenArray[highlightedIndex] + ? childrenArray[highlightedIndex].props.label + : childrenArray.find((child) => child.props.value === value)?.props.label || "Select"; + + // body of the dropdown component + return ( +
        + + {isOpen && ( +
          + {React.Children.map(children, (child, index) => + isReactElement(child) + ? React.cloneElement(child, { + onClick: () => handleOptionClick(child.props.value), + id: `${id}-option-${index}`, + className: index === highlightedIndex ? styles.highlightedOption : "", + }) + : child, + )} +
        + )} +
        + ); +}; diff --git a/webview-ui/src/components/CustomDropdownOption.tsx b/webview-ui/src/components/CustomDropdownOption.tsx new file mode 100644 index 000000000..d8b27e4b6 --- /dev/null +++ b/webview-ui/src/components/CustomDropdownOption.tsx @@ -0,0 +1,25 @@ +import React from "react"; +import styles from "./CustomDropdown.module.css"; + +interface CustomDropdownOptionProps { + id?: string; + value: string; + label: string; + className?: string; + onClick?: (value: string) => void; +} + +// eslint-disable-next-line @typescript-eslint/naming-convention +export const CustomDropdownOption: React.FC = ({ id, value, label, className, onClick }) => { + const handleClick = () => { + if (onClick) { + onClick(value); + } + }; + + return ( +
      • + {label} +
      • + ); +}; diff --git a/webview-ui/src/components/InlineAction.tsx b/webview-ui/src/components/InlineAction.tsx index 0210dbf88..ff3463ea4 100644 --- a/webview-ui/src/components/InlineAction.tsx +++ b/webview-ui/src/components/InlineAction.tsx @@ -1,7 +1,6 @@ import { IconDefinition, faCheckCircle, faClock } from "@fortawesome/free-solid-svg-icons"; import styles from "./InlineAction.module.css"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { VSCodeButton } from "@vscode/webview-ui-toolkit/react"; export function makeFixAction( icon: IconDefinition, @@ -60,9 +59,9 @@ export function InlineAction(props: InlineActionProps) {
        {props.actions.map((action, i) => ( - @@ -70,7 +69,7 @@ export function InlineAction(props: InlineActionProps) { {" "} {action.name} - + ))}
        diff --git a/webview-ui/src/components/NodeSelector.tsx b/webview-ui/src/components/NodeSelector.tsx index ffdaee7e6..4b3a7f224 100644 --- a/webview-ui/src/components/NodeSelector.tsx +++ b/webview-ui/src/components/NodeSelector.tsx @@ -1,5 +1,6 @@ -import { VSCodeDropdown, VSCodeOption } from "@vscode/webview-ui-toolkit/react"; -import { FormEvent } from "react"; +import { CustomDropdown } from "./CustomDropdown"; +import { CustomDropdownOption } from "./CustomDropdownOption"; +import { useState } from "react"; export interface NodeSelectorProps { nodes: string[]; @@ -10,20 +11,19 @@ export interface NodeSelectorProps { } export function NodeSelector(props: NodeSelectorProps) { - function handleNodeChange(e: Event | FormEvent) { - const elem = e.target as HTMLInputElement; - const node = elem.value || null; + const [selectedNode, setSelectedNode] = useState(""); + + function handleNodeChange(node: string) { + setSelectedNode(node); props.onNodeChanged(node); } return ( - - Select + + {props.nodes.map((node) => ( - - {node} - + ))} - + ); } diff --git a/webview-ui/src/components/ResourceSelector.tsx b/webview-ui/src/components/ResourceSelector.tsx index 80d7f743e..aba9e48a5 100644 --- a/webview-ui/src/components/ResourceSelector.tsx +++ b/webview-ui/src/components/ResourceSelector.tsx @@ -1,6 +1,7 @@ -import { FormEvent } from "react"; import { Lazy, asLazy, isLoaded, isLoading, isNotLoaded } from "../utilities/lazy"; -import { VSCodeDropdown, VSCodeOption, VSCodeProgressRing } from "@vscode/webview-ui-toolkit/react"; +import { VSCodeProgressRing } from "@vscode/webview-ui-toolkit/react"; +import { CustomDropdown } from "./CustomDropdown"; +import { CustomDropdownOption } from "./CustomDropdownOption"; export interface ResourceSelectorProps { resources: Lazy | TResource[]; @@ -15,12 +16,9 @@ export interface ResourceSelectorProps { export function ResourceSelector(props: ResourceSelectorProps) { const resources = asLazy(props.resources); - function handleChange(e: Event | FormEvent) { - const elem = e.target as HTMLInputElement; + function handleChange(value: string) { const resource = - elem.value && isLoaded(resources) - ? resources.value.find((r) => props.valueGetter(r) === elem.value) || null - : null; + value && isLoaded(resources) ? resources.value.find((r) => props.valueGetter(r) === value) || null : null; props.onSelect(resource); } @@ -29,22 +27,25 @@ export function ResourceSelector(props: ResourceSelectorProps {isLoading(resources) && } - {isNotLoaded(resources) && } + {isNotLoaded(resources) && ( + {}} + > + )} {isLoaded(resources) && ( - - - Select - + + {resources.value.map((r) => ( - - {props.labelGetter(r)} - + label={props.labelGetter(r)} + /> ))} - + )} ); diff --git a/webview-ui/src/components/TextWithDropdown.module.css b/webview-ui/src/components/TextWithDropdown.module.css index dbad0529e..166b11337 100644 --- a/webview-ui/src/components/TextWithDropdown.module.css +++ b/webview-ui/src/components/TextWithDropdown.module.css @@ -1,8 +1,3 @@ -.selectedValue { - display: inline; /* Prevents a small gap between text input and listbox options */ - width: 100%; -} - .indicator { cursor: pointer; margin-right: -2px; @@ -42,3 +37,25 @@ .listboxItem.selected { background: var(--vscode-list-activeSelectionBackground); } + +.inputField { + position: relative; + width: 100%; +} + +.selectedValue { + width: 100%; + padding-right: 1.8rem !important; + box-sizing: border-box; +} +.selectedValue:hover { + cursor: pointer; +} + +.dropDownButton { + position: absolute; + right: 8px; + top: 50%; + transform: translateY(-50%); + pointer-events: none; +} diff --git a/webview-ui/src/components/TextWithDropdown.tsx b/webview-ui/src/components/TextWithDropdown.tsx index b0b9b2f07..297097c34 100644 --- a/webview-ui/src/components/TextWithDropdown.tsx +++ b/webview-ui/src/components/TextWithDropdown.tsx @@ -1,6 +1,6 @@ import { FormEvent, HTMLAttributes, useEffect, useRef, useState } from "react"; import styles from "./TextWithDropdown.module.css"; -import { VSCodeProgressRing, VSCodeTextField } from "@vscode/webview-ui-toolkit/react"; +import { VSCodeProgressRing } from "@vscode/webview-ui-toolkit/react"; import { Lazy, asLazy, isLoaded, isLoading, isNotLoaded, orDefault } from "../utilities/lazy"; type AvailableHtmlAttributes = Pick, "className" | "id">; @@ -59,7 +59,9 @@ function TextOnly(props: TextOnlyProps) { props.onSelect(newText, true); } - return ; + return ( + + ); } type NonLazyTextWithDropdownProps = Omit & { items: string[] }; @@ -100,13 +102,6 @@ function NonLazyTextWithDropdown(props: NonLazyTextWithDropdownProps) { setIsExpanded(!isExpanded); } - function handleDropDownButtonClick(e: React.MouseEvent) { - // Don't propagate because the containing element (the text field) has its own click handler - // which will itself toggle the expanded state. - e.stopPropagation(); - setIsExpanded(!isExpanded); - } - function handleFocus() { // Work around the fact that focus events are fired before click events. // If we don't delay the expansion, the dropdown will be expanded and then immediately collapsed @@ -204,32 +199,29 @@ function NonLazyTextWithDropdown(props: NonLazyTextWithDropdownProps) { onBlur={handleBlur} onKeyDown={handleKeyDown} > - - - {/* - See: - https://github.com/microsoft/vscode-webview-ui-toolkit/blob/a1f078e963969ad3f6d5932f96874f1a41cda919/src/dropdown/index.ts#L43-L57 - */} - - - - - +
        + + + + +
          li > .item:hover { flex-direction: row; column-gap: 0.5rem; } + +.title { + margin: 0rem 0 0.5rem 0; +} + +.itemSpan { + position: relative; + top: -0.1rem; +} diff --git a/webview-ui/src/manualTest/components/FilePicker.tsx b/webview-ui/src/manualTest/components/FilePicker.tsx index 59335611c..db16d46b0 100644 --- a/webview-ui/src/manualTest/components/FilePicker.tsx +++ b/webview-ui/src/manualTest/components/FilePicker.tsx @@ -1,6 +1,5 @@ import { FormEvent, useState } from "react"; import { Dialog } from "../../components/Dialog"; -import { VSCodeButton, VSCodeCheckbox, VSCodeDivider, VSCodeTextField } from "@vscode/webview-ui-toolkit/react"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { faChevronDown, faChevronRight } from "@fortawesome/free-solid-svg-icons"; import styles from "./FilePicker.module.css"; @@ -113,10 +112,10 @@ export function FilePicker(props: FilePickerProps) { return ( props.closeRequested(null)}> -

          {props.options.title}

          +

          {props.options.title}

          - +
          {(!canSelectMany || selectedItems.length <= 1) && ( -
          - + +
          @@ -235,12 +235,10 @@ function FileSystemNode(props: FileSystemNodeProps) { } function ignoreClick(e: Event | FormEvent) { - e.preventDefault(); e.stopPropagation(); } const isSelected = props.selectedItems.includes(props.item); - const itemClassNames = [styles.item, isSelected ? styles.selected : ""].filter((n) => !!n).join(" "); return ( <> @@ -253,14 +251,20 @@ function FileSystemNode(props: FileSystemNodeProps) { /> )} {props.canSelectMany && !isDirectory(props.item) && ( - props.handleItemSelectionChange(props.item)} - style={{ margin: "0", paddingRight: "0.5rem" }} + style={{ + margin: "0rem 0.5rem 0rem .5rem", + position: "relative", + top: ".125rem", + display: "inline", + }} /> )} - props.handleItemSelectionChange(props.item)} className={itemClassNames}> + props.handleItemSelectionChange(props.item)} className={styles.itemSpan}> {props.item.name} {expanded && isDirectory(props.item) && ( From 72ba4ac9c06184aff51ed4766739b6b2f79eab23 Mon Sep 17 00:00:00 2001 From: tejhan-diallo Date: Tue, 4 Mar 2025 06:15:29 -0500 Subject: [PATCH 2/3] disabled styling & checkbox visibility fix --- webview-ui/src/main.css | 52 +++++++++++++++++++++++++++++++++-------- 1 file changed, 42 insertions(+), 10 deletions(-) diff --git a/webview-ui/src/main.css b/webview-ui/src/main.css index e00b99b67..444245ad4 100644 --- a/webview-ui/src/main.css +++ b/webview-ui/src/main.css @@ -137,6 +137,11 @@ input[type="text"]:read-only { cursor: not-allowed; } +input[type="text"]:disabled { + cursor: not-allowed; + opacity: 0.4; +} + input[type="checkbox"] { width: 18.25px; height: 18.25px; @@ -144,22 +149,49 @@ input[type="checkbox"] { } input[type="radio"] { - box-sizing: border-box; - width: 16px; - height: 16px; - padding: 0; - margin: 0; - border: 2px solid var(--radio-border-color); + -webkit-appearance: none; + appearance: none; + width: 17px; + height: 17px; + background-color: var(--vscode-checkbox-background, none); + border: 1px solid var(--vscode-checkbox-border, --radio-border-color); border-radius: 50%; - background-color: none; + cursor: pointer; outline: none; + color: var(--vscode-checkbox-foreground, white); + margin: 0px; + padding: 0px; position: relative; top: 0.18rem; - color: white; } -input[type="radio"] { - cursor: pointer; +input[type="radio"]:checked::before { + content: ""; + position: absolute; + top: 50%; + left: 50%; + width: 8px; + height: 8px; + background-color: var(--vscode-checkbox-foreground, white); + border-radius: 50%; + transform: translate(-50%, -50%); +} + +input[type="radio"]:focus-visible { + border: 1px solid var(--vscode-focusBorder, #007acc); +} + +input[type="radio"]:active { + border: 1px solid var(--vscode-focusBorder, #007acc); +} + +input[type="radio"]:disabled { + cursor: not-allowed; + opacity: 0.4; +} + +fieldset:disabled label { + opacity: 0.4; } select { From 2ed6b1281512bfa98bb37c2b2e24d5a658cabc9c Mon Sep 17 00:00:00 2001 From: tejhan-diallo Date: Tue, 4 Mar 2025 08:12:57 -0500 Subject: [PATCH 3/3] merge fix --- webview-ui/src/InspektorGadget/Overview.tsx | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/webview-ui/src/InspektorGadget/Overview.tsx b/webview-ui/src/InspektorGadget/Overview.tsx index d33ff7ace..2f1a02778 100644 --- a/webview-ui/src/InspektorGadget/Overview.tsx +++ b/webview-ui/src/InspektorGadget/Overview.tsx @@ -47,12 +47,13 @@ export function Overview(props: OverviewProps) { )} - {props.version && !props.version.server && ( - - )} + {props.version && + (isVersionStringOrNull(props.version.client) || isVersionStringOrNull(props.version.server)) && ( + + )} ); }