diff --git a/coderd/apidoc/docs.go b/coderd/apidoc/docs.go index 406e96ca6661a..cec325a7b87af 100644 --- a/coderd/apidoc/docs.go +++ b/coderd/apidoc/docs.go @@ -14176,6 +14176,9 @@ const docTemplate = `{ "label": { "type": "string" }, + "mask_input": { + "type": "boolean" + }, "placeholder": { "type": "string" } diff --git a/coderd/apidoc/swagger.json b/coderd/apidoc/swagger.json index 561c10fbdf6a1..6841978b127b9 100644 --- a/coderd/apidoc/swagger.json +++ b/coderd/apidoc/swagger.json @@ -12834,6 +12834,9 @@ "label": { "type": "string" }, + "mask_input": { + "type": "boolean" + }, "placeholder": { "type": "string" } diff --git a/coderd/database/db2sdk/db2sdk.go b/coderd/database/db2sdk/db2sdk.go index c74e63bb86f59..5e9be4d61a57c 100644 --- a/coderd/database/db2sdk/db2sdk.go +++ b/coderd/database/db2sdk/db2sdk.go @@ -816,6 +816,7 @@ func PreviewParameter(param previewtypes.Parameter) codersdk.PreviewParameter { Placeholder: param.Styling.Placeholder, Disabled: param.Styling.Disabled, Label: param.Styling.Label, + MaskInput: param.Styling.MaskInput, }, Mutable: param.Mutable, DefaultValue: PreviewHCLString(param.DefaultValue), diff --git a/codersdk/parameters.go b/codersdk/parameters.go index bdf48f2c6e8fa..1e15d0496c1fa 100644 --- a/codersdk/parameters.go +++ b/codersdk/parameters.go @@ -91,6 +91,7 @@ type PreviewParameterStyling struct { Placeholder *string `json:"placeholder,omitempty"` Disabled *bool `json:"disabled,omitempty"` Label *string `json:"label,omitempty"` + MaskInput *bool `json:"mask_input,omitempty"` } type PreviewParameterOption struct { diff --git a/docs/reference/api/schemas.md b/docs/reference/api/schemas.md index 011e19c976a47..efb4be973f055 100644 --- a/docs/reference/api/schemas.md +++ b/docs/reference/api/schemas.md @@ -2890,6 +2890,7 @@ CreateWorkspaceRequest provides options for creating a new workspace. Only one o "styling": { "disabled": true, "label": "string", + "mask_input": true, "placeholder": "string" }, "type": "string", @@ -5059,6 +5060,7 @@ Git clone makes use of this by parsing the URL from: 'Username for "https://gith "styling": { "disabled": true, "label": "string", + "mask_input": true, "placeholder": "string" }, "type": "string", @@ -5128,6 +5130,7 @@ Git clone makes use of this by parsing the URL from: 'Username for "https://gith { "disabled": true, "label": "string", + "mask_input": true, "placeholder": "string" } ``` @@ -5138,6 +5141,7 @@ Git clone makes use of this by parsing the URL from: 'Username for "https://gith |---------------|---------|----------|--------------|-------------| | `disabled` | boolean | false | | | | `label` | string | false | | | +| `mask_input` | boolean | false | | | | `placeholder` | string | false | | | ## codersdk.PreviewParameterValidation diff --git a/docs/reference/api/templates.md b/docs/reference/api/templates.md index dc4605532ff41..cf99922877ab3 100644 --- a/docs/reference/api/templates.md +++ b/docs/reference/api/templates.md @@ -2697,6 +2697,7 @@ curl -X POST http://coder-server:8080/api/v2/templateversions/{templateversion}/ "styling": { "disabled": true, "label": "string", + "mask_input": true, "placeholder": "string" }, "type": "string", diff --git a/go.mod b/go.mod index 923a8d9680951..cd92b8f3a36dd 100644 --- a/go.mod +++ b/go.mod @@ -483,7 +483,7 @@ require ( require ( github.com/coder/agentapi-sdk-go v0.0.0-20250505131810-560d1d88d225 github.com/coder/aisdk-go v0.0.9 - github.com/coder/preview v1.0.1 + github.com/coder/preview v1.0.2 github.com/fsnotify/fsnotify v1.9.0 github.com/mark3labs/mcp-go v0.32.0 ) diff --git a/go.sum b/go.sum index d19b25a80d334..537a2747e797a 100644 --- a/go.sum +++ b/go.sum @@ -916,8 +916,8 @@ github.com/coder/pq v1.10.5-0.20250630052411-a259f96b6102 h1:ahTJlTRmTogsubgRVGO github.com/coder/pq v1.10.5-0.20250630052411-a259f96b6102/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/coder/pretty v0.0.0-20230908205945-e89ba86370e0 h1:3A0ES21Ke+FxEM8CXx9n47SZOKOpgSE1bbJzlE4qPVs= github.com/coder/pretty v0.0.0-20230908205945-e89ba86370e0/go.mod h1:5UuS2Ts+nTToAMeOjNlnHFkPahrtDkmpydBen/3wgZc= -github.com/coder/preview v1.0.1 h1:f6q+RjNelwnkyXfGbmVlb4dcUOQ0z4mPsb2kuQpFHuU= -github.com/coder/preview v1.0.1/go.mod h1:efDWGlO/PZPrvdt5QiDhMtTUTkPxejXo9c0wmYYLLjM= +github.com/coder/preview v1.0.2 h1:ZFfox0PgXcIouB9iWGcZyOtdL0h2a4ju1iPw/dMqsg4= +github.com/coder/preview v1.0.2/go.mod h1:efDWGlO/PZPrvdt5QiDhMtTUTkPxejXo9c0wmYYLLjM= github.com/coder/quartz v0.2.1 h1:QgQ2Vc1+mvzewg2uD/nj8MJ9p9gE+QhGJm+Z+NGnrSE= github.com/coder/quartz v0.2.1/go.mod h1:vsiCc+AHViMKH2CQpGIpFgdHIEQsxwm8yCscqKmzbRA= github.com/coder/retry v1.5.1 h1:iWu8YnD8YqHs3XwqrqsjoBTAVqT9ml6z9ViJ2wlMiqc= diff --git a/site/src/api/typesGenerated.ts b/site/src/api/typesGenerated.ts index 23ed8c72290cc..a29943fafb6ea 100644 --- a/site/src/api/typesGenerated.ts +++ b/site/src/api/typesGenerated.ts @@ -1836,6 +1836,7 @@ export interface PreviewParameterStyling { readonly placeholder?: string; readonly disabled?: boolean; readonly label?: string; + readonly mask_input?: boolean; } // From codersdk/parameters.go diff --git a/site/src/modules/workspaces/DynamicParameter/DynamicParameter.stories.tsx b/site/src/modules/workspaces/DynamicParameter/DynamicParameter.stories.tsx index db3fa2f404c53..6eb5f2f77d2a2 100644 --- a/site/src/modules/workspaces/DynamicParameter/DynamicParameter.stories.tsx +++ b/site/src/modules/workspaces/DynamicParameter/DynamicParameter.stories.tsx @@ -8,18 +8,16 @@ const meta: Meta = { parameters: { layout: "centered", }, + args: { + parameter: MockPreviewParameter, + onChange: () => {}, + }, }; export default meta; type Story = StoryObj; -export const TextInput: Story = { - args: { - parameter: { - ...MockPreviewParameter, - }, - }, -}; +export const TextInput: Story = {}; export const TextArea: Story = { args: { @@ -230,3 +228,30 @@ export const AllBadges: Story = { isPreset: true, }, }; + +export const MaskedInput: Story = { + args: { + parameter: { + ...MockPreviewParameter, + form_type: "input", + styling: { + ...MockPreviewParameter.styling, + placeholder: "Tell me a secret", + mask_input: true, + }, + }, + }, +}; + +export const MaskedTextArea: Story = { + args: { + parameter: { + ...MockPreviewParameter, + form_type: "textarea", + styling: { + ...MockPreviewParameter.styling, + mask_input: true, + }, + }, + }, +}; diff --git a/site/src/modules/workspaces/DynamicParameter/DynamicParameter.tsx b/site/src/modules/workspaces/DynamicParameter/DynamicParameter.tsx index fa9e4759f84d8..5665129eadba3 100644 --- a/site/src/modules/workspaces/DynamicParameter/DynamicParameter.tsx +++ b/site/src/modules/workspaces/DynamicParameter/DynamicParameter.tsx @@ -5,6 +5,7 @@ import type { WorkspaceBuildParameter, } from "api/typesGenerated"; import { Badge } from "components/Badge/Badge"; +import { Button } from "components/Button/Button"; import { Checkbox } from "components/Checkbox/Checkbox"; import { ExternalImage } from "components/ExternalImage/ExternalImage"; import { Input } from "components/Input/Input"; @@ -23,6 +24,7 @@ import { SelectValue, } from "components/Select/Select"; import { Slider } from "components/Slider/Slider"; +import { Stack } from "components/Stack/Stack"; import { Switch } from "components/Switch/Switch"; import { TagInput } from "components/TagInput/TagInput"; import { Textarea } from "components/Textarea/Textarea"; @@ -36,6 +38,8 @@ import { useDebouncedValue } from "hooks/debounce"; import { useEffectEvent } from "hooks/hookPolyfills"; import { CircleAlert, + Eye, + EyeOff, Hourglass, Info, LinkIcon, @@ -43,6 +47,7 @@ import { TriangleAlert, } from "lucide-react"; import { type FC, useEffect, useId, useRef, useState } from "react"; +import { cn } from "utils/cn"; import type { AutofillBuildParameter } from "utils/richParameters"; import * as Yup from "yup"; @@ -265,6 +270,7 @@ const DebouncedParameterField: FC = ({ const [localValue, setLocalValue] = useState( value !== undefined ? value : validValue(parameter.value), ); + const [showMaskedInput, setShowMaskedInput] = useState(false); const debouncedLocalValue = useDebouncedValue(localValue, 500); const onChangeEvent = useEffectEvent(onChange); // prevDebouncedValueRef is to prevent calling the onChangeEvent on the initial render @@ -309,27 +315,56 @@ const DebouncedParameterField: FC = ({ switch (parameter.form_type) { case "textarea": { return ( -