@tangle-network/sandbox-ui 0.118.0 → 0.120.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/pages.js CHANGED
@@ -1,4 +1,4 @@
1
- import { $ as Select, C as DialogTitle$1, G as Input$1, Nt as Textarea$1, Y as PageHeader$1, _ as DialogContent$1, a as Badge$1, b as DialogHeader$1, ct as SelectValue, et as SelectContent, h as Dialog$1, ht as Switch, nt as SelectItem, o as Button$1, st as SelectTrigger, v as DialogDescription$1, y as DialogFooter$1 } from "./primitives-N-59i3-7.js";
1
+ import { $ as Select$1, C as DialogTitle$1, G as Input$1, Nt as Textarea$1, Y as PageHeader$1, _ as DialogContent$1, a as Badge$1, b as DialogHeader$1, ct as SelectValue$1, et as SelectContent$1, h as Dialog$1, ht as Switch, nt as SelectItem$1, o as Button$1, st as SelectTrigger$1, v as DialogDescription$1, y as DialogFooter$1 } from "./primitives-BQGtwZX3.js";
2
2
  import { t as cn$1 } from "./utils-DzWbktWG.js";
3
3
  import { i as PricingPage, n as TemplateCard, o as BillingDashboard, r as UsageChart, t as InfoPanel } from "./info-panel-C9cqDzOL.js";
4
4
  import { Badge, Button, Card, Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, EmptyState, Input, Skeleton, SkeletonCard, Tabs, TabsContent, TabsList, TabsTrigger } from "@tangle-network/ui/primitives";
@@ -1490,25 +1490,25 @@ function ProvisioningWizard({ environments: environmentsProp, onLoadEnvironments
1490
1490
  htmlFor: `${fieldIdPrefix}-driver`,
1491
1491
  className: cn$1(FIELD_LABEL_CLASS, "mb-1.5"),
1492
1492
  children: "Virtualization Driver"
1493
- }), /* @__PURE__ */ jsxs(Select, {
1493
+ }), /* @__PURE__ */ jsxs(Select$1, {
1494
1494
  value: driver,
1495
1495
  onValueChange: (value) => {
1496
1496
  if (VALID_DRIVERS.has(value)) setDriver(value);
1497
1497
  },
1498
- children: [/* @__PURE__ */ jsx(SelectTrigger, {
1498
+ children: [/* @__PURE__ */ jsx(SelectTrigger$1, {
1499
1499
  id: `${fieldIdPrefix}-driver`,
1500
1500
  className: "h-9",
1501
- children: /* @__PURE__ */ jsx(SelectValue, {})
1502
- }), /* @__PURE__ */ jsxs(SelectContent, { children: [
1503
- /* @__PURE__ */ jsx(SelectItem, {
1501
+ children: /* @__PURE__ */ jsx(SelectValue$1, {})
1502
+ }), /* @__PURE__ */ jsxs(SelectContent$1, { children: [
1503
+ /* @__PURE__ */ jsx(SelectItem$1, {
1504
1504
  value: "docker",
1505
1505
  children: "Docker container (Default)"
1506
1506
  }),
1507
- /* @__PURE__ */ jsx(SelectItem, {
1507
+ /* @__PURE__ */ jsx(SelectItem$1, {
1508
1508
  value: "firecracker",
1509
1509
  children: "Firecracker microVM (Secure)"
1510
1510
  }),
1511
- /* @__PURE__ */ jsx(SelectItem, {
1511
+ /* @__PURE__ */ jsx(SelectItem$1, {
1512
1512
  value: "tangle",
1513
1513
  children: "Tangle Distributed Node"
1514
1514
  })
@@ -2698,7 +2698,13 @@ function parseEnvText(text) {
2698
2698
  //#region src/pages/secrets-page.tsx
2699
2699
  /** Cap pasted/uploaded import sources so a pathological file cannot blow up the parser/UI. */
2700
2700
  const MAX_IMPORT_FILE_BYTES = 262144;
2701
- function SecretsPage({ apiClient, className, teamSecretsHint }) {
2701
+ const DEFAULT_DESCRIPTION = "Secrets are securely stored and automatically exposed as environment variables across all your sandboxes.";
2702
+ const DEFAULT_CREATE_DESCRIPTION = "Secrets are automatically exposed as environment variables across all your new sandboxes.";
2703
+ const DEFAULT_EMPTY_DESCRIPTION = "Create a secret to inject into your sandboxes.";
2704
+ const DEFAULT_DELETE_CONSEQUENCE = "Sandboxes using this secret will lose access to it.";
2705
+ function SecretsPage({ apiClient, className, teamSecretsHint, title = "Environment Secrets", description = DEFAULT_DESCRIPTION, createDescription = DEFAULT_CREATE_DESCRIPTION, emptyDescription = DEFAULT_EMPTY_DESCRIPTION, deleteConsequence = DEFAULT_DELETE_CONSEQUENCE, variant = "page" }) {
2706
+ const pane = variant === "pane";
2707
+ const canReplace = typeof apiClient.updateSecret === "function";
2702
2708
  const [secrets, setSecrets] = React$1.useState([]);
2703
2709
  const [loading, setLoading] = React$1.useState(true);
2704
2710
  const [error, setError] = React$1.useState(null);
@@ -2708,6 +2714,7 @@ function SecretsPage({ apiClient, className, teamSecretsHint }) {
2708
2714
  const [showValue, setShowValue] = React$1.useState(false);
2709
2715
  const [isCreating, setIsCreating] = React$1.useState(false);
2710
2716
  const [createError, setCreateError] = React$1.useState(null);
2717
+ const [replaceTarget, setReplaceTarget] = React$1.useState(null);
2711
2718
  const [deleteTarget, setDeleteTarget] = React$1.useState(null);
2712
2719
  const [isDeleting, setIsDeleting] = React$1.useState(false);
2713
2720
  const [isImportOpen, setIsImportOpen] = React$1.useState(false);
@@ -2740,16 +2747,39 @@ function SecretsPage({ apiClient, className, teamSecretsHint }) {
2740
2747
  React$1.useEffect(() => {
2741
2748
  loadSecrets();
2742
2749
  }, [loadSecrets]);
2750
+ const openCreate = () => {
2751
+ setReplaceTarget(null);
2752
+ setNewName("");
2753
+ setNewValue("");
2754
+ setCreateError(null);
2755
+ setShowValue(false);
2756
+ setIsCreateOpen(true);
2757
+ };
2758
+ const openReplace = (name) => {
2759
+ setReplaceTarget(name);
2760
+ setNewName(name);
2761
+ setNewValue("");
2762
+ setCreateError(null);
2763
+ setShowValue(false);
2764
+ setIsCreateOpen(true);
2765
+ };
2766
+ const closeCreate = () => {
2767
+ setIsCreateOpen(false);
2768
+ setReplaceTarget(null);
2769
+ setNewName("");
2770
+ setNewValue("");
2771
+ setCreateError(null);
2772
+ setShowValue(false);
2773
+ };
2743
2774
  const handleCreate = async () => {
2744
2775
  if (!newName.trim() || !newValue.trim()) return;
2745
2776
  setIsCreating(true);
2746
2777
  setCreateError(null);
2747
2778
  try {
2748
- await apiRef.current.createSecret(newName.trim(), newValue);
2749
- setIsCreateOpen(false);
2750
- setNewName("");
2751
- setNewValue("");
2752
- setShowValue(false);
2779
+ const update = apiRef.current.updateSecret;
2780
+ if (replaceTarget && update) await update(replaceTarget, newValue);
2781
+ else await apiRef.current.createSecret(newName.trim(), newValue);
2782
+ closeCreate();
2753
2783
  await loadSecrets(false);
2754
2784
  } catch (err) {
2755
2785
  setCreateError(err instanceof Error ? err.message : "Failed to create secret");
@@ -2886,26 +2916,48 @@ function SecretsPage({ apiClient, className, teamSecretsHint }) {
2886
2916
  return dateStr;
2887
2917
  }
2888
2918
  };
2919
+ const actions = /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsxs("button", {
2920
+ type: "button",
2921
+ onClick: () => setIsImportOpen(true),
2922
+ className: "inline-flex items-center gap-1.5 whitespace-nowrap rounded-lg border border-[var(--md3-outline-variant)] bg-surface-container px-3 py-2 text-sm font-semibold text-foreground transition-colors hover:bg-surface-container-high active:scale-[0.97] sm:gap-2",
2923
+ children: [/* @__PURE__ */ jsx(Upload, {
2924
+ className: "h-4 w-4",
2925
+ "aria-hidden": "true"
2926
+ }), "Import .env"]
2927
+ }), /* @__PURE__ */ jsxs("button", {
2928
+ type: "button",
2929
+ onClick: openCreate,
2930
+ className: "inline-flex items-center gap-1.5 whitespace-nowrap rounded-lg border border-[var(--border-accent,transparent)] bg-[var(--btn-primary-bg)] px-3 py-2 text-sm font-semibold text-[var(--btn-primary-text)] transition-colors hover:bg-[var(--btn-primary-hover)] active:scale-[0.97] sm:gap-2 sm:px-4",
2931
+ children: [/* @__PURE__ */ jsx(Plus, {
2932
+ className: "h-4 w-4",
2933
+ "aria-hidden": "true"
2934
+ }), "New Secret"]
2935
+ })] });
2889
2936
  return /* @__PURE__ */ jsxs("div", {
2890
- className: cn$1("mx-auto w-full max-w-6xl space-y-8", className),
2937
+ className: cn$1(pane ? "flex h-full min-h-0 w-full flex-col" : "mx-auto w-full max-w-6xl space-y-6", className),
2891
2938
  children: [
2892
- /* @__PURE__ */ jsx(PageHeader$1, {
2893
- title: "Environment Secrets",
2894
- description: "Secrets are securely stored and automatically exposed as environment variables across all your sandboxes.",
2895
- action: /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsxs("button", {
2896
- type: "button",
2897
- onClick: () => setIsImportOpen(true),
2898
- className: "inline-flex items-center gap-1.5 whitespace-nowrap rounded-lg border border-[var(--md3-outline-variant)] bg-surface-container px-3 py-2.5 text-sm font-semibold text-foreground transition-colors hover:bg-surface-container-high active:scale-[0.97] sm:gap-2 sm:px-4",
2899
- children: [/* @__PURE__ */ jsx(Upload, { className: "h-4 w-4" }), "Import .env"]
2900
- }), /* @__PURE__ */ jsxs("button", {
2901
- type: "button",
2902
- onClick: () => setIsCreateOpen(true),
2903
- className: "inline-flex items-center gap-1.5 whitespace-nowrap rounded-lg border border-[var(--border-accent,transparent)] bg-[var(--btn-primary-bg)] px-3 py-2.5 text-sm font-semibold text-[var(--btn-primary-text)] transition-colors hover:bg-[var(--btn-primary-hover)] active:scale-[0.97] sm:gap-2 sm:px-5",
2904
- children: [/* @__PURE__ */ jsx(Plus, { className: "h-4 w-4" }), "New Secret"]
2905
- })] })
2939
+ pane ? /* @__PURE__ */ jsxs("header", {
2940
+ className: "flex shrink-0 flex-wrap items-center justify-between gap-x-6 gap-y-3 border-b border-border px-4 py-4 sm:px-6",
2941
+ children: [/* @__PURE__ */ jsxs("div", {
2942
+ className: "min-w-0 flex-1 basis-64",
2943
+ children: [/* @__PURE__ */ jsx("h1", {
2944
+ className: "text-lg font-semibold tracking-tight text-foreground",
2945
+ children: title
2946
+ }), description && /* @__PURE__ */ jsx("p", {
2947
+ className: "mt-0.5 text-sm text-muted-foreground",
2948
+ children: description
2949
+ })]
2950
+ }), /* @__PURE__ */ jsx("div", {
2951
+ className: "flex shrink-0 items-center gap-2",
2952
+ children: actions
2953
+ })]
2954
+ }) : /* @__PURE__ */ jsx(PageHeader$1, {
2955
+ title,
2956
+ description,
2957
+ action: actions
2906
2958
  }),
2907
2959
  teamSecretsHint && /* @__PURE__ */ jsxs("div", {
2908
- className: "flex flex-wrap items-center gap-3 rounded-lg border border-[var(--md3-outline-variant)] bg-[var(--accent-surface-soft)]/40 px-4 py-3",
2960
+ className: cn$1("flex flex-wrap items-center gap-3 border-[var(--md3-outline-variant)] bg-[var(--accent-surface-soft)]/40 px-4 py-3", pane ? "shrink-0 border-b sm:px-6" : "rounded-lg border"),
2909
2961
  children: [/* @__PURE__ */ jsxs("div", {
2910
2962
  className: "flex min-w-[min(100%,18rem)] flex-1 items-start gap-3",
2911
2963
  children: [/* @__PURE__ */ jsx(Users, {
@@ -2935,65 +2987,33 @@ function SecretsPage({ apiClient, className, teamSecretsHint }) {
2935
2987
  })]
2936
2988
  })]
2937
2989
  }),
2938
- /* @__PURE__ */ jsxs("div", {
2939
- className: "grid grid-cols-1 gap-6 md:grid-cols-4",
2940
- children: [
2941
- /* @__PURE__ */ jsxs("div", {
2942
- className: "rounded-lg border border-[var(--md3-outline-variant)] bg-surface-container p-5 shadow-[var(--shadow-card)]",
2943
- children: [/* @__PURE__ */ jsx("p", {
2944
- className: "text-[10px] font-bold uppercase tracking-widest text-muted-foreground",
2945
- children: "Total Active Secrets"
2946
- }), /* @__PURE__ */ jsx("div", {
2947
- className: "mt-2 flex items-baseline gap-2",
2948
- children: /* @__PURE__ */ jsx("span", {
2949
- className: "font-display text-2xl font-extrabold text-foreground",
2950
- children: secrets.length
2951
- })
2952
- })]
2953
- }),
2954
- /* @__PURE__ */ jsxs("div", {
2955
- className: "rounded-lg border border-[var(--md3-outline-variant)] bg-surface-container p-5 shadow-[var(--shadow-card)]",
2956
- children: [/* @__PURE__ */ jsx("p", {
2957
- className: "text-[10px] font-bold uppercase tracking-widest text-muted-foreground",
2958
- children: "Status"
2959
- }), /* @__PURE__ */ jsxs("div", {
2960
- className: "mt-2 flex items-center gap-2",
2961
- children: [/* @__PURE__ */ jsx(CheckCircle, { className: "h-4 w-4 text-[var(--surface-success-text,#047857)]" }), /* @__PURE__ */ jsx("span", {
2962
- className: "text-sm font-semibold text-[var(--surface-success-text,#047857)]",
2963
- children: "Encrypted"
2964
- })]
2965
- })]
2966
- }),
2967
- /* @__PURE__ */ jsx(InfoPanel, {
2968
- className: "md:col-span-2",
2969
- label: "Security Audit",
2970
- title: "All engines operational.",
2971
- description: "Secrets are encrypted at rest using AES-256."
2972
- })
2973
- ]
2974
- }),
2975
- error && /* @__PURE__ */ jsxs("div", {
2976
- className: "rounded-lg border border-destructive/30 bg-destructive/10 p-4 flex items-center gap-3",
2977
- children: [/* @__PURE__ */ jsx(AlertCircle, { className: "h-5 w-5 text-destructive shrink-0" }), /* @__PURE__ */ jsx("p", {
2978
- className: "text-destructive text-sm font-medium",
2990
+ error && (secrets.length > 0 || loading) && /* @__PURE__ */ jsxs("div", {
2991
+ role: "alert",
2992
+ className: cn$1("flex items-center gap-3 border-destructive/30 bg-destructive/10 p-4", pane ? "shrink-0 border-b sm:px-6" : "rounded-lg border"),
2993
+ children: [/* @__PURE__ */ jsx(AlertCircle, {
2994
+ className: "h-5 w-5 shrink-0 text-destructive",
2995
+ "aria-hidden": "true"
2996
+ }), /* @__PURE__ */ jsx("p", {
2997
+ className: "text-sm font-medium text-destructive",
2979
2998
  children: error
2980
2999
  })]
2981
3000
  }),
2982
3001
  /* @__PURE__ */ jsx(Dialog, {
2983
3002
  open: isCreateOpen,
2984
3003
  onOpenChange: (open) => {
2985
- if (!open) {
2986
- setIsCreateOpen(false);
2987
- setNewName("");
2988
- setNewValue("");
2989
- setCreateError(null);
2990
- setShowValue(false);
2991
- }
3004
+ if (!open) closeCreate();
2992
3005
  },
2993
3006
  children: /* @__PURE__ */ jsxs(DialogContent, {
2994
3007
  className: "max-w-md",
2995
3008
  children: [
2996
- /* @__PURE__ */ jsxs(DialogHeader, { children: [/* @__PURE__ */ jsx(DialogTitle, { children: "Create Secret" }), /* @__PURE__ */ jsx(DialogDescription, { children: "Secrets are automatically exposed as environment variables across all your new sandboxes." })] }),
3009
+ /* @__PURE__ */ jsxs(DialogHeader, { children: [/* @__PURE__ */ jsx(DialogTitle, { children: replaceTarget ? "Replace Value" : "Create Secret" }), /* @__PURE__ */ jsx(DialogDescription, { children: replaceTarget ? /* @__PURE__ */ jsxs(Fragment, { children: [
3010
+ "Save a new value for ",
3011
+ /* @__PURE__ */ jsx("span", {
3012
+ className: "font-mono font-bold text-foreground",
3013
+ children: replaceTarget
3014
+ }),
3015
+ ". The current value is overwritten and cannot be recovered."
3016
+ ] }) : createDescription })] }),
2997
3017
  /* @__PURE__ */ jsxs("form", {
2998
3018
  onSubmit: (e) => {
2999
3019
  e.preventDefault();
@@ -3013,6 +3033,8 @@ function SecretsPage({ apiClient, className, teamSecretsHint }) {
3013
3033
  onChange: (e) => setNewName(e.target.value.toUpperCase().replace(/[^A-Z0-9_]/g, "_")),
3014
3034
  placeholder: "MY_SECRET_KEY",
3015
3035
  autoComplete: "off",
3036
+ readOnly: replaceTarget !== null,
3037
+ "aria-readonly": replaceTarget !== null,
3016
3038
  className: `w-full rounded-md border bg-surface-container-low px-3 py-2.5 text-sm font-mono text-foreground placeholder:text-muted-foreground ${focusField}`
3017
3039
  })] }),
3018
3040
  /* @__PURE__ */ jsxs("div", { children: [
@@ -3031,6 +3053,7 @@ function SecretsPage({ apiClient, className, teamSecretsHint }) {
3031
3053
  onChange: (e) => setNewValue(e.target.value),
3032
3054
  placeholder: "Enter secret value...",
3033
3055
  autoComplete: "new-password",
3056
+ autoFocus: replaceTarget !== null,
3034
3057
  className: `w-full rounded-md border bg-surface-container-low px-3 py-2.5 pr-10 text-sm font-mono text-foreground placeholder:text-muted-foreground ${focusField}`
3035
3058
  }), /* @__PURE__ */ jsx("button", {
3036
3059
  type: "button",
@@ -3042,7 +3065,7 @@ function SecretsPage({ apiClient, className, teamSecretsHint }) {
3042
3065
  }),
3043
3066
  /* @__PURE__ */ jsx("p", {
3044
3067
  className: "mt-1.5 text-xs text-muted-foreground",
3045
- children: "This value cannot be retrieved after creation."
3068
+ children: "This value cannot be retrieved after it is saved."
3046
3069
  })
3047
3070
  ] }),
3048
3071
  /* @__PURE__ */ jsx("button", {
@@ -3060,12 +3083,7 @@ function SecretsPage({ apiClient, className, teamSecretsHint }) {
3060
3083
  }),
3061
3084
  /* @__PURE__ */ jsxs(DialogFooter, { children: [/* @__PURE__ */ jsx("button", {
3062
3085
  type: "button",
3063
- onClick: () => {
3064
- setIsCreateOpen(false);
3065
- setNewName("");
3066
- setNewValue("");
3067
- setCreateError(null);
3068
- },
3086
+ onClick: closeCreate,
3069
3087
  className: "rounded-md border border-[var(--md3-outline-variant)] bg-surface-container px-4 py-2 text-sm font-medium text-foreground hover:bg-surface-container-high transition-colors",
3070
3088
  children: "Cancel"
3071
3089
  }), /* @__PURE__ */ jsx("button", {
@@ -3073,7 +3091,7 @@ function SecretsPage({ apiClient, className, teamSecretsHint }) {
3073
3091
  onClick: handleCreate,
3074
3092
  disabled: !newName.trim() || !newValue.trim() || isCreating,
3075
3093
  className: "rounded-md bg-[var(--btn-primary-bg)] px-4 py-2 text-sm font-bold text-[var(--btn-primary-text)] hover:bg-[var(--btn-primary-hover)] transition-colors disabled:opacity-50 active:scale-[0.97]",
3076
- children: isCreating ? "Creating..." : "Create Secret"
3094
+ children: isCreating ? "Saving..." : replaceTarget ? "Replace Value" : "Create Secret"
3077
3095
  })] })
3078
3096
  ]
3079
3097
  })
@@ -3292,7 +3310,8 @@ function SecretsPage({ apiClient, className, teamSecretsHint }) {
3292
3310
  className: "font-mono font-bold text-foreground",
3293
3311
  children: deleteTarget
3294
3312
  }),
3295
- ". Sandboxes using this secret will lose access to it."
3313
+ ". ",
3314
+ deleteConsequence
3296
3315
  ] })] }), /* @__PURE__ */ jsxs(DialogFooter, { children: [/* @__PURE__ */ jsx("button", {
3297
3316
  type: "button",
3298
3317
  onClick: () => setDeleteTarget(null),
@@ -3307,160 +3326,188 @@ function SecretsPage({ apiClient, className, teamSecretsHint }) {
3307
3326
  })] })]
3308
3327
  })
3309
3328
  }),
3310
- /* @__PURE__ */ jsxs("div", {
3311
- className: "overflow-hidden rounded-lg border border-[var(--md3-outline-variant)] bg-surface-container shadow-[var(--shadow-card)]",
3312
- children: [/* @__PURE__ */ jsxs("div", {
3313
- className: "border-b border-[var(--md3-outline-variant)] px-6 py-4 flex items-center justify-between",
3314
- children: [/* @__PURE__ */ jsx("div", {
3315
- className: "flex gap-6",
3316
- children: /* @__PURE__ */ jsx("button", {
3329
+ /* @__PURE__ */ jsx("section", {
3330
+ "aria-label": title,
3331
+ className: cn$1(pane ? "min-h-0 flex-1 overflow-y-auto" : "overflow-hidden rounded-lg border border-[var(--md3-outline-variant)] bg-surface-container shadow-[var(--shadow-card)]"),
3332
+ children: loading ? /* @__PURE__ */ jsxs("div", {
3333
+ role: "status",
3334
+ "aria-busy": "true",
3335
+ className: "divide-y divide-border",
3336
+ children: [/* @__PURE__ */ jsx("span", {
3337
+ className: "sr-only",
3338
+ children: "Loading secrets…"
3339
+ }), [
3340
+ 44,
3341
+ 36,
3342
+ 52
3343
+ ].map((width) => /* @__PURE__ */ jsxs("div", {
3344
+ className: "flex items-center gap-3 px-4 py-4 sm:px-6",
3345
+ "aria-hidden": "true",
3346
+ children: [/* @__PURE__ */ jsx("div", { className: "h-4 w-4 animate-pulse rounded bg-muted" }), /* @__PURE__ */ jsx("div", {
3347
+ className: "h-4 animate-pulse rounded bg-muted",
3348
+ style: { width: `${width * 4}px` }
3349
+ })]
3350
+ }, width))]
3351
+ }) : error && secrets.length === 0 ? /* @__PURE__ */ jsxs("div", {
3352
+ role: "alert",
3353
+ className: "flex flex-col items-center justify-center px-6 py-16 text-center",
3354
+ children: [
3355
+ /* @__PURE__ */ jsx(AlertCircle, {
3356
+ className: "mb-4 h-10 w-10 text-destructive",
3357
+ "aria-hidden": "true"
3358
+ }),
3359
+ /* @__PURE__ */ jsx("h3", {
3360
+ className: "text-base font-semibold text-foreground",
3361
+ children: "Secrets could not load"
3362
+ }),
3363
+ /* @__PURE__ */ jsx("p", {
3364
+ className: "mt-1 max-w-sm text-sm text-muted-foreground",
3365
+ children: error
3366
+ }),
3367
+ /* @__PURE__ */ jsx("button", {
3317
3368
  type: "button",
3318
- className: "text-xs font-bold uppercase tracking-widest text-foreground border-b-2 border-foreground pb-1",
3319
- children: "All Secrets"
3369
+ onClick: () => void loadSecrets(),
3370
+ className: "mt-6 rounded-md border border-[var(--md3-outline-variant)] bg-surface-container px-4 py-2 text-sm font-semibold text-foreground transition-colors hover:bg-surface-container-high",
3371
+ children: "Retry"
3320
3372
  })
3321
- }), /* @__PURE__ */ jsxs("span", {
3322
- className: "text-xs text-muted-foreground font-mono",
3323
- children: [
3324
- secrets.length,
3325
- " secret",
3326
- secrets.length !== 1 ? "s" : ""
3327
- ]
3328
- })]
3329
- }), loading ? /* @__PURE__ */ jsx("div", {
3330
- className: "flex items-center justify-center py-16",
3331
- children: /* @__PURE__ */ jsx("div", {
3332
- className: "h-6 w-6 animate-spin rounded-full border-2 border-muted-foreground border-t-transparent",
3333
- "data-motion": "essential"
3334
- })
3373
+ ]
3335
3374
  }) : secrets.length === 0 ? /* @__PURE__ */ jsxs("div", {
3336
- className: "flex flex-col items-center justify-center py-16 text-center",
3375
+ className: "flex flex-col items-center justify-center px-6 py-16 text-center",
3337
3376
  children: [
3338
- /* @__PURE__ */ jsx(Lock, { className: "h-10 w-10 text-muted-foreground mb-4" }),
3377
+ /* @__PURE__ */ jsx(Lock, {
3378
+ className: "mb-4 h-10 w-10 text-muted-foreground",
3379
+ "aria-hidden": "true"
3380
+ }),
3339
3381
  /* @__PURE__ */ jsx("h3", {
3340
3382
  className: "text-lg font-semibold text-foreground",
3341
3383
  children: "No secrets yet"
3342
3384
  }),
3343
3385
  /* @__PURE__ */ jsx("p", {
3344
- className: "mt-1 text-sm text-muted-foreground max-w-sm",
3345
- children: "Create a secret to inject into your sandboxes."
3386
+ className: "mt-1 max-w-sm text-sm text-muted-foreground",
3387
+ children: emptyDescription
3346
3388
  }),
3347
3389
  /* @__PURE__ */ jsxs("button", {
3348
3390
  type: "button",
3349
- onClick: () => setIsCreateOpen(true),
3391
+ onClick: openCreate,
3350
3392
  "aria-label": "Create your first secret",
3351
- className: "mt-6 inline-flex items-center gap-2 rounded-md bg-[var(--btn-primary-bg)] px-4 py-2 text-sm font-semibold text-[var(--btn-primary-text)] hover:bg-[var(--btn-primary-hover)] transition-colors active:scale-[0.97]",
3352
- children: [/* @__PURE__ */ jsx(Plus, { className: "h-4 w-4" }), "New Secret"]
3393
+ className: "mt-6 inline-flex items-center gap-2 rounded-md bg-[var(--btn-primary-bg)] px-4 py-2 text-sm font-semibold text-[var(--btn-primary-text)] transition-colors hover:bg-[var(--btn-primary-hover)] active:scale-[0.97]",
3394
+ children: [/* @__PURE__ */ jsx(Plus, {
3395
+ className: "h-4 w-4",
3396
+ "aria-hidden": "true"
3397
+ }), "New Secret"]
3353
3398
  })
3354
3399
  ]
3355
3400
  }) : /* @__PURE__ */ jsxs("table", {
3356
- className: "w-full text-left border-collapse",
3357
- children: [/* @__PURE__ */ jsx("thead", { children: /* @__PURE__ */ jsxs("tr", {
3358
- className: "bg-surface-container-high border-b border-[var(--md3-outline-variant)]",
3359
- children: [
3360
- /* @__PURE__ */ jsx("th", {
3361
- scope: "col",
3362
- className: "px-4 py-3 text-[10px] font-bold uppercase tracking-widest text-muted-foreground sm:px-6 sm:py-4",
3363
- children: "Secret Name"
3364
- }),
3365
- /* @__PURE__ */ jsx("th", {
3366
- scope: "col",
3367
- className: "hidden px-6 py-4 text-[10px] font-bold uppercase tracking-widest text-muted-foreground sm:table-cell",
3368
- children: "Encrypted Value"
3369
- }),
3370
- /* @__PURE__ */ jsx("th", {
3371
- scope: "col",
3372
- className: "hidden px-6 py-4 text-right text-[10px] font-bold uppercase tracking-widest text-muted-foreground sm:table-cell",
3373
- children: "Created"
3374
- }),
3375
- /* @__PURE__ */ jsx("th", {
3376
- scope: "col",
3377
- "aria-label": "Actions",
3378
- className: "w-12 px-3 py-3 sm:px-6 sm:py-4"
3379
- })
3380
- ]
3381
- }) }), /* @__PURE__ */ jsx("tbody", {
3382
- className: "divide-y divide-border",
3383
- children: secrets.map((secret) => /* @__PURE__ */ jsxs("tr", {
3384
- className: "hover:bg-surface-container-high transition-colors",
3401
+ className: "w-full border-collapse text-left",
3402
+ children: [
3403
+ /* @__PURE__ */ jsxs("caption", {
3404
+ className: "sr-only",
3385
3405
  children: [
3386
- /* @__PURE__ */ jsx("td", {
3387
- className: "px-4 py-3 sm:px-6 sm:py-4",
3388
- children: /* @__PURE__ */ jsxs("div", {
3389
- className: "flex items-center gap-3",
3390
- children: [/* @__PURE__ */ jsx(Key, { className: "h-4 w-4 shrink-0 text-muted-foreground" }), /* @__PURE__ */ jsxs("div", {
3391
- className: "min-w-0",
3392
- children: [/* @__PURE__ */ jsx("span", {
3393
- className: "break-all font-mono text-sm font-bold text-foreground sm:break-normal",
3394
- children: secret.name
3395
- }), /* @__PURE__ */ jsxs("span", {
3396
- className: "mt-1 block text-xs text-muted-foreground sm:hidden",
3397
- children: ["Created ", formatDate(secret.createdAt)]
3398
- })]
3406
+ secrets.length,
3407
+ " secret",
3408
+ secrets.length !== 1 ? "s" : ""
3409
+ ]
3410
+ }),
3411
+ /* @__PURE__ */ jsx("thead", {
3412
+ className: cn$1(pane && "sticky top-0 z-10 bg-background"),
3413
+ children: /* @__PURE__ */ jsxs("tr", {
3414
+ className: "border-b border-[var(--md3-outline-variant)]",
3415
+ children: [
3416
+ /* @__PURE__ */ jsxs("th", {
3417
+ scope: "col",
3418
+ className: "px-4 py-2.5 text-xs font-medium text-muted-foreground sm:px-6",
3419
+ children: ["Name ", /* @__PURE__ */ jsx("span", {
3420
+ className: "ml-1 font-mono tabular-nums",
3421
+ children: secrets.length
3399
3422
  })]
3423
+ }),
3424
+ /* @__PURE__ */ jsx("th", {
3425
+ scope: "col",
3426
+ className: "hidden px-4 py-2.5 text-xs font-medium text-muted-foreground md:table-cell",
3427
+ children: "Value"
3428
+ }),
3429
+ /* @__PURE__ */ jsx("th", {
3430
+ scope: "col",
3431
+ className: "hidden px-4 py-2.5 text-right text-xs font-medium text-muted-foreground sm:table-cell",
3432
+ children: "Updated"
3433
+ }),
3434
+ /* @__PURE__ */ jsx("th", {
3435
+ scope: "col",
3436
+ className: "w-px px-4 py-2.5 sm:pr-6",
3437
+ children: /* @__PURE__ */ jsx("span", {
3438
+ className: "sr-only",
3439
+ children: "Actions"
3440
+ })
3400
3441
  })
3401
- }),
3402
- /* @__PURE__ */ jsx("td", {
3403
- className: "hidden px-6 py-4 sm:table-cell",
3404
- children: /* @__PURE__ */ jsx("code", {
3405
- className: "text-xs font-mono text-muted-foreground bg-surface-container-high px-2 py-1 rounded",
3406
- children: "••••••••••••••••"
3407
- })
3408
- }),
3409
- /* @__PURE__ */ jsx("td", {
3410
- className: "hidden px-6 py-4 text-right sm:table-cell",
3411
- children: /* @__PURE__ */ jsx("span", {
3412
- className: "text-xs text-muted-foreground",
3413
- children: formatDate(secret.createdAt)
3414
- })
3415
- }),
3416
- /* @__PURE__ */ jsx("td", {
3417
- className: "px-3 py-3 sm:px-6 sm:py-4",
3418
- children: /* @__PURE__ */ jsx("button", {
3419
- type: "button",
3420
- onClick: () => setDeleteTarget(secret.name),
3421
- className: "p-1.5 rounded-md text-muted-foreground hover:text-destructive hover:bg-destructive/10 transition-colors",
3422
- "aria-label": `Delete ${secret.name}`,
3423
- children: /* @__PURE__ */ jsx(Trash2, { className: "h-4 w-4" })
3424
- })
3425
- })
3426
- ]
3427
- }, secret.name))
3428
- })]
3429
- })]
3430
- }),
3431
- /* @__PURE__ */ jsxs("div", {
3432
- className: "grid grid-cols-1 gap-6 md:grid-cols-2",
3433
- children: [/* @__PURE__ */ jsxs("div", {
3434
- className: "rounded-lg border border-[var(--md3-outline-variant)] bg-surface-container p-6 shadow-[var(--shadow-card)]",
3435
- children: [/* @__PURE__ */ jsxs("div", {
3436
- className: "flex items-center gap-3 mb-3",
3437
- children: [/* @__PURE__ */ jsx("div", {
3438
- className: "flex h-9 w-9 items-center justify-center rounded-md bg-[var(--brand-primary,hsl(var(--primary)))] text-[var(--btn-primary-text)]",
3439
- children: /* @__PURE__ */ jsx(Shield, { className: "h-5 w-5" })
3440
- }), /* @__PURE__ */ jsx("h3", {
3441
- className: "text-sm font-bold text-foreground",
3442
- children: "Encryption Standard"
3443
- })]
3444
- }), /* @__PURE__ */ jsx("p", {
3445
- className: "text-sm text-muted-foreground leading-relaxed",
3446
- children: "Your secrets are encrypted using AES-256-GCM at rest and TLS 1.3 in transit. Hardware Security Modules manage all root keys."
3447
- })]
3448
- }), /* @__PURE__ */ jsxs("div", {
3449
- className: "rounded-lg border border-[var(--md3-outline-variant)] bg-surface-container p-6 shadow-[var(--shadow-card)]",
3450
- children: [/* @__PURE__ */ jsxs("div", {
3451
- className: "flex items-center gap-3 mb-3",
3452
- children: [/* @__PURE__ */ jsx("div", {
3453
- className: "flex h-9 w-9 items-center justify-center rounded-md bg-[var(--brand-primary,hsl(var(--primary)))] text-[var(--btn-primary-text)]",
3454
- children: /* @__PURE__ */ jsx(Lock, { className: "h-5 w-5" })
3455
- }), /* @__PURE__ */ jsx("h3", {
3456
- className: "text-sm font-bold text-foreground",
3457
- children: "Access Policy"
3458
- })]
3459
- }), /* @__PURE__ */ jsx("p", {
3460
- className: "text-sm text-muted-foreground leading-relaxed",
3461
- children: "Secrets are injected at sandbox creation time and are never exposed in logs, API responses, or container metadata."
3462
- })]
3463
- })]
3442
+ ]
3443
+ })
3444
+ }),
3445
+ /* @__PURE__ */ jsx("tbody", {
3446
+ className: "divide-y divide-border",
3447
+ children: secrets.map((secret) => {
3448
+ const changedAt = formatDate(secret.updatedAt ?? secret.createdAt);
3449
+ return /* @__PURE__ */ jsxs("tr", {
3450
+ className: "transition-colors hover:bg-surface-container-high",
3451
+ children: [
3452
+ /* @__PURE__ */ jsx("td", {
3453
+ className: "py-3 pl-4 pr-1 sm:px-6",
3454
+ children: /* @__PURE__ */ jsxs("div", {
3455
+ className: "flex min-w-0 items-center gap-2.5 sm:gap-3",
3456
+ children: [/* @__PURE__ */ jsx(Key, {
3457
+ className: "h-4 w-4 shrink-0 text-muted-foreground",
3458
+ "aria-hidden": "true"
3459
+ }), /* @__PURE__ */ jsxs("div", {
3460
+ className: "min-w-0",
3461
+ children: [/* @__PURE__ */ jsx("span", {
3462
+ className: "font-mono text-[13px] font-semibold text-foreground [overflow-wrap:anywhere] sm:text-sm",
3463
+ children: secret.name
3464
+ }), /* @__PURE__ */ jsxs("span", {
3465
+ className: "mt-0.5 block text-xs text-muted-foreground sm:hidden",
3466
+ children: ["Updated ", changedAt]
3467
+ })]
3468
+ })]
3469
+ })
3470
+ }),
3471
+ /* @__PURE__ */ jsx("td", {
3472
+ className: "hidden px-4 py-3 md:table-cell",
3473
+ children: /* @__PURE__ */ jsx("span", {
3474
+ className: "font-mono text-xs tracking-widest text-muted-foreground",
3475
+ "aria-label": "Value hidden",
3476
+ children: "••••••••••••"
3477
+ })
3478
+ }),
3479
+ /* @__PURE__ */ jsx("td", {
3480
+ className: "hidden whitespace-nowrap px-4 py-3 text-right text-xs text-muted-foreground sm:table-cell",
3481
+ children: changedAt
3482
+ }),
3483
+ /* @__PURE__ */ jsx("td", {
3484
+ className: "whitespace-nowrap py-3 pl-1 pr-3 sm:px-4 sm:pr-6",
3485
+ children: /* @__PURE__ */ jsxs("div", {
3486
+ className: "flex items-center justify-end gap-1",
3487
+ children: [canReplace && /* @__PURE__ */ jsx("button", {
3488
+ type: "button",
3489
+ onClick: () => openReplace(secret.name),
3490
+ className: "rounded-md px-2 py-1.5 text-xs font-semibold text-foreground transition-colors hover:bg-surface-container-high",
3491
+ "aria-label": `Replace value of ${secret.name}`,
3492
+ children: "Replace"
3493
+ }), /* @__PURE__ */ jsx("button", {
3494
+ type: "button",
3495
+ onClick: () => setDeleteTarget(secret.name),
3496
+ className: "rounded-md p-1.5 text-muted-foreground transition-colors hover:bg-destructive/10 hover:text-destructive",
3497
+ "aria-label": `Delete ${secret.name}`,
3498
+ children: /* @__PURE__ */ jsx(Trash2, {
3499
+ className: "h-4 w-4",
3500
+ "aria-hidden": "true"
3501
+ })
3502
+ })]
3503
+ })
3504
+ })
3505
+ ]
3506
+ }, secret.name);
3507
+ })
3508
+ })
3509
+ ]
3510
+ })
3464
3511
  })
3465
3512
  ]
3466
3513
  });