Showing the changes to apps (3 of 10 files). Show all
Unified · Split
| @@ -1,10 +1,11 @@ | ||
| 1 | −import { useState, type ReactNode } from "react"; | |
| 1 | +import { useEffect, useState, type ReactNode } from "react"; | |
| 2 | 2 | import { Link, useNavigate } from "@tanstack/react-router"; |
| 3 | 3 | import { |
| 4 | 4 | type CompleteSourceOAuthResult, |
| 5 | 5 | type ConnectSourcePayload, |
| 6 | 6 | type ConnectSourceResult, |
| 7 | 7 | type DiscoverSourcePayload, |
| 8 | + type InstanceConfig, | |
| 8 | 9 | type Loadable, |
| 9 | 10 | type SecretListItem, |
| 10 | 11 | type Source, |
| @@ -12,6 +13,7 @@ import { · 1 unmodified line | ||
| 12 | 13 | useConnectSource, |
| 13 | 14 | useCreateSecret, |
| 14 | 15 | useDiscoverSource, |
| 16 | + useInstanceConfig, | |
| 15 | 17 | useRefreshSecrets, |
| 16 | 18 | useSecrets, |
| 17 | 19 | useStartSourceOAuth, |
| @@ -590,6 +592,7 @@ export function AddSourcePage() { · 572 unmodified lines | ||
| 590 | 592 | const navigate = useNavigate(); |
| 591 | 593 | const discoverSource = useDiscoverSource(); |
| 592 | 594 | const connectSource = useConnectSource(); |
| 595 | + const instanceConfig = useInstanceConfig(); | |
| 593 | 596 | const secrets = useSecrets(); |
| 594 | 597 | const refreshSecrets = useRefreshSecrets(); |
| 595 | 598 | const startSourceOAuth = useStartSourceOAuth(); |
| @@ -1209,6 +1212,7 @@ export function AddSourcePage() { · 613 unmodified lines | ||
| 1209 | 1212 | {connectForm.authKind === "bearer" && ( |
| 1210 | 1213 | <Field label="Token" className="sm:col-span-2"> |
| 1211 | 1214 | <SecretOrTokenInput |
| 1215 | + instanceConfig={instanceConfig} | |
| 1212 | 1216 | secrets={secrets} |
| 1213 | 1217 | providerId={connectForm.bearerProviderId} |
| 1214 | 1218 | handle={connectForm.bearerHandle} |
| @@ -1408,6 +1412,7 @@ const CREATE_NEW_VALUE = "__create_new__"; · 193 unmodified lines | ||
| 1408 | 1412 | const INLINE_TOKEN_VALUE = "__inline_token__"; |
| 1409 | 1413 | |
| 1410 | 1414 | function SecretOrTokenInput(props: { |
| 1415 | + instanceConfig: Loadable<InstanceConfig>; | |
| 1411 | 1416 | secrets: Loadable<ReadonlyArray<SecretListItem>>; |
| 1412 | 1417 | providerId: string; |
| 1413 | 1418 | handle: string; |
| @@ -1415,13 +1420,35 @@ function SecretOrTokenInput(props: { · 1 unmodified line | ||
| 1415 | 1420 | onSelectSecret: (providerId: string, handle: string) => void; |
| 1416 | 1421 | onChangeToken: (token: string) => void; |
| 1417 | 1422 | }) { |
| 1418 | − const { secrets, providerId, handle, inlineToken, onSelectSecret, onChangeToken } = props; | |
| 1423 | + const { | |
| 1424 | + instanceConfig, | |
| 1425 | + secrets, | |
| 1426 | + providerId, | |
| 1427 | + handle, | |
| 1428 | + inlineToken, | |
| 1429 | + onSelectSecret, | |
| 1430 | + onChangeToken, | |
| 1431 | + } = props; | |
| 1419 | 1432 | const createSecret = useCreateSecret(); |
| 1420 | 1433 | const [showCreate, setShowCreate] = useState(false); |
| 1421 | 1434 | const [useInline, setUseInline] = useState(false); |
| 1422 | 1435 | const [newName, setNewName] = useState(""); |
| 1423 | 1436 | const [newValue, setNewValue] = useState(""); |
| 1437 | + const [newProviderId, setNewProviderId] = useState(""); | |
| 1424 | 1438 | const [createError, setCreateError] = useState<string | null>(null); |
| 1439 | + const storableProviders = | |
| 1440 | + instanceConfig.status === "ready" | |
| 1441 | + ? instanceConfig.data.secretProviders.filter((provider) => provider.canStore) | |
| 1442 | + : []; | |
| 1443 | + | |
| 1444 | + useEffect(() => { | |
| 1445 | + if (newProviderId.length > 0) { | |
| 1446 | + return; | |
| 1447 | + } | |
| 1448 | + if (instanceConfig.status === "ready") { | |
| 1449 | + setNewProviderId(instanceConfig.data.defaultSecretStoreProvider); | |
| 1450 | + } | |
| 1451 | + }, [instanceConfig, newProviderId]); | |
| 1425 | 1452 | |
| 1426 | 1453 | const selectedValue = showCreate ? CREATE_NEW_VALUE : useInline ? INLINE_TOKEN_VALUE : (handle || ""); |
| 1427 | 1454 | |
| @@ -1431,6 +1458,11 @@ function SecretOrTokenInput(props: { · 3 unmodified lines | ||
| 1431 | 1458 | setUseInline(false); |
| 1432 | 1459 | setNewName(""); |
| 1433 | 1460 | setNewValue(""); |
| 1461 | + setNewProviderId( | |
| 1462 | + instanceConfig.status === "ready" | |
| 1463 | + ? instanceConfig.data.defaultSecretStoreProvider | |
| 1464 | + : "", | |
| 1465 | + ); | |
| 1434 | 1466 | setCreateError(null); |
| 1435 | 1467 | return; |
| 1436 | 1468 | } |
| @@ -1448,7 +1480,10 @@ function SecretOrTokenInput(props: { · 11 unmodified lines | ||
| 1448 | 1480 | } |
| 1449 | 1481 | setUseInline(false); |
| 1450 | 1482 | setShowCreate(false); |
| 1451 | − onSelectSecret("postgres", value); | |
| 1483 | + const matchedSecret = secrets.status === "ready" | |
| 1484 | + ? secrets.data.find((secret) => secret.id === value) | |
| 1485 | + : null; | |
| 1486 | + onSelectSecret(matchedSecret?.providerId ?? "local", value); | |
| 1452 | 1487 | }; |
| 1453 | 1488 | |
| 1454 | 1489 | const handleCreate = async () => { |
| @@ -1464,7 +1499,11 @@ function SecretOrTokenInput(props: { · 9 unmodified lines | ||
| 1464 | 1499 | } |
| 1465 | 1500 | |
| 1466 | 1501 | try { |
| 1467 | − const result = await createSecret.mutateAsync({ name: trimmedName, value: newValue }); | |
| 1502 | + const result = await createSecret.mutateAsync({ | |
| 1503 | + name: trimmedName, | |
| 1504 | + value: newValue, | |
| 1505 | + ...(newProviderId ? { providerId: newProviderId } : {}), | |
| 1506 | + }); | |
| 1468 | 1507 | onSelectSecret(result.providerId, result.id); |
| 1469 | 1508 | setShowCreate(false); |
| 1470 | 1509 | } catch (err) { |
| @@ -1495,7 +1534,7 @@ function SecretOrTokenInput(props: { · 24 unmodified lines | ||
| 1495 | 1534 | <option value="">Select a secret...</option> |
| 1496 | 1535 | {items.map((secret) => ( |
| 1497 | 1536 | <option key={secret.id} value={secret.id}> |
| 1498 | − {secret.name || secret.id} | |
| 1537 | + {secret.name || secret.id} ({secret.providerId}) | |
| 1499 | 1538 | </option> |
| 1500 | 1539 | ))} |
| 1501 | 1540 | <option value={INLINE_TOKEN_VALUE}>Paste token directly</option> |
| @@ -1541,6 +1580,20 @@ function SecretOrTokenInput(props: { · 39 unmodified lines | ||
| 1541 | 1580 | className="h-8 w-full rounded-lg border border-input bg-background px-3 font-mono text-[11px] text-foreground outline-none transition-colors placeholder:text-muted-foreground/35 focus:border-ring focus:ring-1 focus:ring-ring/25" |
| 1542 | 1581 | /> |
| 1543 | 1582 | </label> |
| 1583 | + <label className="block space-y-1"> | |
| 1584 | + <span className="text-[11px] font-medium text-muted-foreground">Store in</span> | |
| 1585 | + <select | |
| 1586 | + value={newProviderId} | |
| 1587 | + onChange={(e) => setNewProviderId(e.target.value)} | |
| 1588 | + className="h-8 w-full rounded-lg border border-input bg-background px-3 text-[12px] text-foreground outline-none transition-colors focus:border-ring focus:ring-1 focus:ring-ring/25" | |
| 1589 | + > | |
| 1590 | + {storableProviders.map((provider) => ( | |
| 1591 | + <option key={provider.id} value={provider.id}> | |
| 1592 | + {provider.name} | |
| 1593 | + </option> | |
| 1594 | + ))} | |
| 1595 | + </select> | |
| 1596 | + </label> | |
| 1544 | 1597 | </div> |
| 1545 | 1598 | <div className="flex items-center justify-end gap-2"> |
| 1546 | 1599 | <button |
| @@ -1,13 +1,14 @@ | ||
| 1 | −import { useState, type ReactNode } from "react"; | |
| 1 | +import { useEffect, useState, type ReactNode } from "react"; | |
| 2 | 2 | import { Link } from "@tanstack/react-router"; |
| 3 | 3 | import { |
| 4 | + type InstanceConfig, | |
| 4 | 5 | type SecretListItem, |
| 5 | 6 | type Loadable, |
| 6 | 7 | useSecrets, |
| 7 | 8 | useCreateSecret, |
| 8 | 9 | useUpdateSecret, |
| 9 | 10 | useDeleteSecret, |
| 10 | − useRefreshSecrets, | |
| 11 | + useInstanceConfig, | |
| 11 | 12 | } from "@executor/react"; |
| 12 | 13 | import { LoadableBlock } from "../components/loadable"; |
| 13 | 14 | import { Button } from "../components/ui/button"; |
| @@ -19,10 +20,26 @@ import { cn } from "../lib/utils"; · 5 unmodified lines | ||
| 19 | 20 | // Page |
| 20 | 21 | // --------------------------------------------------------------------------- |
| 21 | 22 | |
| 23 | +const storableProvidersFromConfig = (config: Loadable<InstanceConfig>) => | |
| 24 | + config.status === "ready" | |
| 25 | + ? config.data.secretProviders.filter((provider) => provider.canStore) | |
| 26 | + : []; | |
| 27 | + | |
| 28 | +const providerLabelForId = (config: Loadable<InstanceConfig>, providerId: string): string => | |
| 29 | + config.status === "ready" | |
| 30 | + ? config.data.secretProviders.find((provider) => provider.id === providerId)?.name ?? providerId | |
| 31 | + : providerId; | |
| 32 | + | |
| 22 | 33 | export function SecretsPage() { |
| 34 | + const instanceConfig = useInstanceConfig(); | |
| 23 | 35 | const secrets = useSecrets(); |
| 24 | 36 | const [showCreate, setShowCreate] = useState(false); |
| 25 | 37 | const [editingId, setEditingId] = useState<string | null>(null); |
| 38 | + const storableProviders = storableProvidersFromConfig(instanceConfig); | |
| 39 | + const defaultProviderLabel = | |
| 40 | + instanceConfig.status === "ready" | |
| 41 | + ? providerLabelForId(instanceConfig, instanceConfig.data.defaultSecretStoreProvider) | |
| 42 | + : null; | |
| 26 | 43 | |
| 27 | 44 | return ( |
| 28 | 45 | <div className="flex-1 overflow-y-auto"> |
| @@ -34,7 +51,8 @@ export function SecretsPage() { · 5 unmodified lines | ||
| 34 | 51 | Secrets |
| 35 | 52 | </h1> |
| 36 | 53 | <p className="mt-1.5 text-[14px] text-muted-foreground"> |
| 37 | − Tokens and credentials stored in this instance. | |
| 54 | + Tokens and credentials stored by executor. | |
| 55 | + {defaultProviderLabel ? ` New secrets default to ${defaultProviderLabel}.` : ""} | |
| 38 | 56 | </p> |
| 39 | 57 | </div> |
| 40 | 58 | <Button size="sm" onClick={() => { setShowCreate(true); setEditingId(null); }}> |
| @@ -48,6 +66,12 @@ export function SecretsPage() { · 7 unmodified lines | ||
| 48 | 66 | <CreateSecretForm |
| 49 | 67 | onClose={() => setShowCreate(false)} |
| 50 | 68 | className="mb-6" |
| 69 | + providerOptions={storableProviders} | |
| 70 | + defaultProviderId={ | |
| 71 | + instanceConfig.status === "ready" | |
| 72 | + ? instanceConfig.data.defaultSecretStoreProvider | |
| 73 | + : null | |
| 74 | + } | |
| 51 | 75 | /> |
| 52 | 76 | )} |
| 53 | 77 | |
| @@ -62,6 +86,7 @@ export function SecretsPage() { · 8 unmodified lines | ||
| 62 | 86 | <SecretRow |
| 63 | 87 | key={secret.id} |
| 64 | 88 | secret={secret} |
| 89 | + providerLabel={providerLabelForId(instanceConfig, secret.providerId)} | |
| 65 | 90 | isEditing={editingId === secret.id} |
| 66 | 91 | onEdit={() => setEditingId(editingId === secret.id ? null : secret.id)} |
| 67 | 92 | onCancelEdit={() => setEditingId(null)} |
| @@ -95,7 +120,7 @@ function EmptyState(props: { onAdd: () => void }) { · 27 unmodified lines | ||
| 95 | 120 | No secrets stored |
| 96 | 121 | </p> |
| 97 | 122 | <p className="text-[13px] text-muted-foreground/60 mb-5"> |
| 98 | − Secrets let you securely provide tokens for source authentication. | |
| 123 | + Secrets let you securely provide tokens for source authentication without putting them in workspace config. | |
| 99 | 124 | </p> |
| 100 | 125 | <Button size="sm" onClick={props.onAdd}> |
| 101 | 126 | <IconPlus className="size-3.5" /> |
| @@ -109,12 +134,27 @@ function EmptyState(props: { onAdd: () => void }) { · 7 unmodified lines | ||
| 109 | 134 | // Create form |
| 110 | 135 | // --------------------------------------------------------------------------- |
| 111 | 136 | |
| 112 | −function CreateSecretForm(props: { onClose: () => void; className?: string }) { | |
| 137 | +function CreateSecretForm(props: { | |
| 138 | + onClose: () => void; | |
| 139 | + className?: string; | |
| 140 | + providerOptions: ReadonlyArray<{ id: string; name: string }>; | |
| 141 | + defaultProviderId: string | null; | |
| 142 | +}) { | |
| 113 | 143 | const createSecret = useCreateSecret(); |
| 114 | 144 | const [name, setName] = useState(""); |
| 115 | 145 | const [value, setValue] = useState(""); |
| 146 | + const [providerId, setProviderId] = useState(props.defaultProviderId ?? ""); | |
| 116 | 147 | const [error, setError] = useState<string | null>(null); |
| 117 | 148 | |
| 149 | + useEffect(() => { | |
| 150 | + if (providerId.length > 0) { | |
| 151 | + return; | |
| 152 | + } | |
| 153 | + if (props.defaultProviderId) { | |
| 154 | + setProviderId(props.defaultProviderId); | |
| 155 | + } | |
| 156 | + }, [props.defaultProviderId, providerId]); | |
| 157 | + | |
| 118 | 158 | const handleSubmit = async () => { |
| 119 | 159 | setError(null); |
| 120 | 160 | const trimmedName = name.trim(); |
| @@ -128,7 +168,11 @@ function CreateSecretForm(props: { onClose: () => void; className?: string }) { · 7 unmodified lines | ||
| 128 | 168 | } |
| 129 | 169 | |
| 130 | 170 | try { |
| 131 | − await createSecret.mutateAsync({ name: trimmedName, value }); | |
| 171 | + await createSecret.mutateAsync({ | |
| 172 | + name: trimmedName, | |
| 173 | + value, | |
| 174 | + ...(providerId ? { providerId } : {}), | |
| 175 | + }); | |
| 132 | 176 | props.onClose(); |
| 133 | 177 | } catch (err) { |
| 134 | 178 | setError(err instanceof Error ? err.message : "Failed creating secret."); |
| @@ -172,6 +216,19 @@ function CreateSecretForm(props: { onClose: () => void; className?: string }) { · 37 unmodified lines | ||
| 172 | 216 | className="h-9 w-full rounded-lg border border-input bg-background px-3 font-mono text-[12px] text-foreground outline-none transition-colors placeholder:text-muted-foreground/35 focus:border-ring focus:ring-1 focus:ring-ring/25" |
| 173 | 217 | /> |
| 174 | 218 | </FieldLabel> |
| 219 | + <FieldLabel label="Store in"> | |
| 220 | + <select | |
| 221 | + value={providerId} | |
| 222 | + onChange={(e) => setProviderId(e.target.value)} | |
| 223 | + className="h-9 w-full rounded-lg border border-input bg-background px-3 text-[13px] text-foreground outline-none transition-colors focus:border-ring focus:ring-1 focus:ring-ring/25" | |
| 224 | + > | |
| 225 | + {props.providerOptions.map((provider) => ( | |
| 226 | + <option key={provider.id} value={provider.id}> | |
| 227 | + {provider.name} | |
| 228 | + </option> | |
| 229 | + ))} | |
| 230 | + </select> | |
| 231 | + </FieldLabel> | |
| 175 | 232 | </div> |
| 176 | 233 | <div className="flex justify-end"> |
| 177 | 234 | <Button |
| @@ -194,11 +251,12 @@ function CreateSecretForm(props: { onClose: () => void; className?: string }) { · 16 unmodified lines | ||
| 194 | 251 | |
| 195 | 252 | function SecretRow(props: { |
| 196 | 253 | secret: SecretListItem; |
| 254 | + providerLabel: string; | |
| 197 | 255 | isEditing: boolean; |
| 198 | 256 | onEdit: () => void; |
| 199 | 257 | onCancelEdit: () => void; |
| 200 | 258 | }) { |
| 201 | − const { secret, isEditing } = props; | |
| 259 | + const { secret, providerLabel, isEditing } = props; | |
| 202 | 260 | const deleteSecret = useDeleteSecret(); |
| 203 | 261 | const [isDeleting, setIsDeleting] = useState(false); |
| 204 | 262 | |
| @@ -239,6 +297,9 @@ function SecretRow(props: { · 34 unmodified lines | ||
| 239 | 297 | <Badge variant="outline" className="text-[9px] shrink-0"> |
| 240 | 298 | {secret.purpose.replace(/_/g, " ")} |
| 241 | 299 | </Badge> |
| 300 | + <Badge variant="outline" className="text-[9px] shrink-0"> | |
| 301 | + {providerLabel} | |
| 302 | + </Badge> | |
| 242 | 303 | </div> |
| 243 | 304 | <div className="flex items-center gap-2 mt-0.5"> |
| 244 | 305 | <span className="text-[11px] font-mono text-muted-foreground/50 truncate"> |
| @@ -1,4 +1,4 @@ | ||
| 1 | −import { useState, type ReactNode } from "react"; | |
| 1 | +import { useEffect, useState, type ReactNode } from "react"; | |
| 2 | 2 | import { Link, useNavigate } from "@tanstack/react-router"; |
| 3 | 3 | import { |
| 4 | 4 | type CompleteSourceOAuthResult, |
| @@ -1012,6 +1012,7 @@ function SourceEditor(props: { mode: "create" | "edit"; source?: Source }) { · 1007 unmodified lines | ||
| 1012 | 1012 | {formState.authKind === "bearer" && ( |
| 1013 | 1013 | <Field label="Token" className="sm:col-span-2"> |
| 1014 | 1014 | <SecretPicker |
| 1015 | + instanceConfig={instanceConfig} | |
| 1015 | 1016 | secrets={secrets} |
| 1016 | 1017 | providerId={formState.bearerProviderId} |
| 1017 | 1018 | handle={formState.bearerHandle} |
| @@ -1030,6 +1031,7 @@ function SourceEditor(props: { mode: "create" | "edit"; source?: Source }) { · 12 unmodified lines | ||
| 1030 | 1031 | <> |
| 1031 | 1032 | <Field label="Access token" className="sm:col-span-2"> |
| 1032 | 1033 | <SecretPicker |
| 1034 | + instanceConfig={instanceConfig} | |
| 1033 | 1035 | secrets={secrets} |
| 1034 | 1036 | providerId={formState.oauthAccessProviderId} |
| 1035 | 1037 | handle={formState.oauthAccessHandle} |
| @@ -1041,6 +1043,7 @@ function SourceEditor(props: { mode: "create" | "edit"; source?: Source }) { · 5 unmodified lines | ||
| 1041 | 1043 | </Field> |
| 1042 | 1044 | <Field label="Refresh token (optional)" className="sm:col-span-2"> |
| 1043 | 1045 | <SecretPicker |
| 1046 | + instanceConfig={instanceConfig} | |
| 1044 | 1047 | secrets={secrets} |
| 1045 | 1048 | providerId={formState.oauthRefreshProviderId} |
| 1046 | 1049 | handle={formState.oauthRefreshHandle} |
| @@ -1218,18 +1221,33 @@ function StatusBanner(props: { state: StatusBannerState; className?: string }) { · 171 unmodified lines | ||
| 1218 | 1221 | const CREATE_NEW_VALUE = "__create_new__"; |
| 1219 | 1222 | |
| 1220 | 1223 | function SecretPicker(props: { |
| 1224 | + instanceConfig: Loadable<InstanceConfig>; | |
| 1221 | 1225 | secrets: Loadable<ReadonlyArray<SecretListItem>>; |
| 1222 | 1226 | providerId: string; |
| 1223 | 1227 | handle: string; |
| 1224 | 1228 | onSelect: (providerId: string, handle: string) => void; |
| 1225 | 1229 | allowEmpty?: boolean; |
| 1226 | 1230 | }) { |
| 1227 | − const { secrets, providerId, handle, onSelect, allowEmpty } = props; | |
| 1231 | + const { instanceConfig, secrets, providerId, handle, onSelect, allowEmpty } = props; | |
| 1228 | 1232 | const createSecret = useCreateSecret(); |
| 1229 | 1233 | const [showCreate, setShowCreate] = useState(false); |
| 1230 | 1234 | const [newName, setNewName] = useState(""); |
| 1231 | 1235 | const [newValue, setNewValue] = useState(""); |
| 1236 | + const [newProviderId, setNewProviderId] = useState(""); | |
| 1232 | 1237 | const [createError, setCreateError] = useState<string | null>(null); |
| 1238 | + const storableProviders = | |
| 1239 | + instanceConfig.status === "ready" | |
| 1240 | + ? instanceConfig.data.secretProviders.filter((provider) => provider.canStore) | |
| 1241 | + : []; | |
| 1242 | + | |
| 1243 | + useEffect(() => { | |
| 1244 | + if (newProviderId.length > 0) { | |
| 1245 | + return; | |
| 1246 | + } | |
| 1247 | + if (instanceConfig.status === "ready") { | |
| 1248 | + setNewProviderId(instanceConfig.data.defaultSecretStoreProvider); | |
| 1249 | + } | |
| 1250 | + }, [instanceConfig, newProviderId]); | |
| 1233 | 1251 | |
| 1234 | 1252 | // Build the selected value key: if handle is set, use it (it's the secret ID) |
| 1235 | 1253 | const selectedValue = handle || ""; |
| @@ -1239,6 +1257,11 @@ function SecretPicker(props: { · 3 unmodified lines | ||
| 1239 | 1257 | setShowCreate(true); |
| 1240 | 1258 | setNewName(""); |
| 1241 | 1259 | setNewValue(""); |
| 1260 | + setNewProviderId( | |
| 1261 | + instanceConfig.status === "ready" | |
| 1262 | + ? instanceConfig.data.defaultSecretStoreProvider | |
| 1263 | + : "", | |
| 1264 | + ); | |
| 1242 | 1265 | setCreateError(null); |
| 1243 | 1266 | return; |
| 1244 | 1267 | } |
| @@ -1246,8 +1269,10 @@ function SecretPicker(props: { · 1 unmodified line | ||
| 1246 | 1269 | onSelect("", ""); |
| 1247 | 1270 | return; |
| 1248 | 1271 | } |
| 1249 | − // Selecting an existing secret: providerId is always "postgres" | |
| 1250 | − onSelect("postgres", value); | |
| 1272 | + const matchedSecret = secrets.status === "ready" | |
| 1273 | + ? secrets.data.find((secret) => secret.id === value) | |
| 1274 | + : null; | |
| 1275 | + onSelect(matchedSecret?.providerId ?? "local", value); | |
| 1251 | 1276 | }; |
| 1252 | 1277 | |
| 1253 | 1278 | const handleCreate = async () => { |
| @@ -1263,7 +1288,11 @@ function SecretPicker(props: { · 9 unmodified lines | ||
| 1263 | 1288 | } |
| 1264 | 1289 | |
| 1265 | 1290 | try { |
| 1266 | − const result = await createSecret.mutateAsync({ name: trimmedName, value: newValue }); | |
| 1291 | + const result = await createSecret.mutateAsync({ | |
| 1292 | + name: trimmedName, | |
| 1293 | + value: newValue, | |
| 1294 | + ...(newProviderId ? { providerId: newProviderId } : {}), | |
| 1295 | + }); | |
| 1267 | 1296 | onSelect(result.providerId, result.id); |
| 1268 | 1297 | setShowCreate(false); |
| 1269 | 1298 | } catch (err) { |
| @@ -1286,7 +1315,7 @@ function SecretPicker(props: { · 16 unmodified lines | ||
| 1286 | 1315 | |
| 1287 | 1316 | // Check if the current handle matches a known secret |
| 1288 | 1317 | const matchedSecret = items.find((s) => s.id === handle); |
| 1289 | − const isExternalRef = handle && !matchedSecret && providerId !== "postgres"; | |
| 1318 | + const isExternalRef = handle && !matchedSecret && providerId !== "local"; | |
| 1290 | 1319 | |
| 1291 | 1320 | return ( |
| 1292 | 1321 | <div className="space-y-2"> |
| @@ -1299,7 +1328,7 @@ function SecretPicker(props: { · 6 unmodified lines | ||
| 1299 | 1328 | {!allowEmpty && !selectedValue && <option value="">Select a secret…</option>} |
| 1300 | 1329 | {items.map((secret) => ( |
| 1301 | 1330 | <option key={secret.id} value={secret.id}> |
| 1302 | − {secret.name || secret.id} | |
| 1331 | + {secret.name || secret.id} ({secret.providerId}) | |
| 1303 | 1332 | </option> |
| 1304 | 1333 | ))} |
| 1305 | 1334 | {isExternalRef && ( |
| @@ -1338,6 +1367,20 @@ function SecretPicker(props: { · 32 unmodified lines | ||
| 1338 | 1367 | className="h-8 w-full rounded-lg border border-input bg-background px-3 font-mono text-[11px] text-foreground outline-none transition-colors placeholder:text-muted-foreground/35 focus:border-ring focus:ring-1 focus:ring-ring/25" |
| 1339 | 1368 | /> |
| 1340 | 1369 | </label> |
| 1370 | + <label className="block space-y-1"> | |
| 1371 | + <span className="text-[11px] font-medium text-muted-foreground">Store in</span> | |
| 1372 | + <select | |
| 1373 | + value={newProviderId} | |
| 1374 | + onChange={(e) => setNewProviderId(e.target.value)} | |
| 1375 | + className="h-8 w-full rounded-lg border border-input bg-background px-3 text-[12px] text-foreground outline-none transition-colors focus:border-ring focus:ring-1 focus:ring-ring/25" | |
| 1376 | + > | |
| 1377 | + {storableProviders.map((provider) => ( | |
| 1378 | + <option key={provider.id} value={provider.id}> | |
| 1379 | + {provider.name} | |
| 1380 | + </option> | |
| 1381 | + ))} | |
| 1382 | + </select> | |
| 1383 | + </label> | |
| 1341 | 1384 | </div> |
| 1342 | 1385 | <div className="flex items-center justify-end gap-2"> |
| 1343 | 1386 | <button |