@flanksource/clicky-ui 0.2.12 → 0.2.13
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/components/Combobox.cjs +47 -33
- package/dist/components/Combobox.cjs.map +1 -1
- package/dist/components/Combobox.d.ts +12 -0
- package/dist/components/Combobox.d.ts.map +1 -1
- package/dist/components/Combobox.js +48 -34
- package/dist/components/Combobox.js.map +1 -1
- package/dist/components/IconButton.cjs +26 -0
- package/dist/components/IconButton.cjs.map +1 -0
- package/dist/components/IconButton.d.ts +25 -0
- package/dist/components/IconButton.d.ts.map +1 -0
- package/dist/components/IconButton.js +26 -0
- package/dist/components/IconButton.js.map +1 -0
- package/dist/components/SecretKeySelector.cjs +151 -0
- package/dist/components/SecretKeySelector.cjs.map +1 -0
- package/dist/components/SecretKeySelector.d.ts +47 -0
- package/dist/components/SecretKeySelector.d.ts.map +1 -0
- package/dist/components/SecretKeySelector.js +151 -0
- package/dist/components/SecretKeySelector.js.map +1 -0
- package/dist/components/WorkloadPicker.cjs +144 -0
- package/dist/components/WorkloadPicker.cjs.map +1 -0
- package/dist/components/WorkloadPicker.d.ts +52 -0
- package/dist/components/WorkloadPicker.d.ts.map +1 -0
- package/dist/components/WorkloadPicker.js +144 -0
- package/dist/components/WorkloadPicker.js.map +1 -0
- package/dist/components/icon-menu-picker.cjs +83 -112
- package/dist/components/icon-menu-picker.cjs.map +1 -1
- package/dist/components/icon-menu-picker.d.ts.map +1 -1
- package/dist/components/icon-menu-picker.js +85 -114
- package/dist/components/icon-menu-picker.js.map +1 -1
- package/dist/components.cjs +10 -0
- package/dist/components.cjs.map +1 -1
- package/dist/components.d.ts +3 -0
- package/dist/components.d.ts.map +1 -1
- package/dist/components.js +11 -1
- package/dist/components.js.map +1 -1
- package/dist/data/Clicky.cjs +1 -1
- package/dist/data/Clicky.cjs.map +1 -1
- package/dist/data/Clicky.js +1 -1
- package/dist/data/Clicky.js.map +1 -1
- package/dist/data/Version.cjs +42 -0
- package/dist/data/Version.cjs.map +1 -0
- package/dist/data/Version.d.ts +36 -0
- package/dist/data/Version.d.ts.map +1 -0
- package/dist/data/Version.js +42 -0
- package/dist/data/Version.js.map +1 -0
- package/dist/data/test-runner/TestDetailPanel.cjs +165 -0
- package/dist/data/test-runner/TestDetailPanel.cjs.map +1 -0
- package/dist/data/test-runner/TestDetailPanel.d.ts +7 -0
- package/dist/data/test-runner/TestDetailPanel.d.ts.map +1 -0
- package/dist/data/test-runner/TestDetailPanel.js +165 -0
- package/dist/data/test-runner/TestDetailPanel.js.map +1 -0
- package/dist/data/test-runner/TestFailureDetail.cjs +78 -0
- package/dist/data/test-runner/TestFailureDetail.cjs.map +1 -0
- package/dist/data/test-runner/TestFailureDetail.d.ts +14 -0
- package/dist/data/test-runner/TestFailureDetail.d.ts.map +1 -0
- package/dist/data/test-runner/TestFailureDetail.js +78 -0
- package/dist/data/test-runner/TestFailureDetail.js.map +1 -0
- package/dist/data/test-runner/TestFilterBar.cjs +108 -0
- package/dist/data/test-runner/TestFilterBar.cjs.map +1 -0
- package/dist/data/test-runner/TestFilterBar.d.ts +15 -0
- package/dist/data/test-runner/TestFilterBar.d.ts.map +1 -0
- package/dist/data/test-runner/TestFilterBar.js +108 -0
- package/dist/data/test-runner/TestFilterBar.js.map +1 -0
- package/dist/data/test-runner/TestRunSummary.cjs +81 -0
- package/dist/data/test-runner/TestRunSummary.cjs.map +1 -0
- package/dist/data/test-runner/TestRunSummary.d.ts +19 -0
- package/dist/data/test-runner/TestRunSummary.d.ts.map +1 -0
- package/dist/data/test-runner/TestRunSummary.js +81 -0
- package/dist/data/test-runner/TestRunSummary.js.map +1 -0
- package/dist/data/test-runner/TestRunner.cjs +143 -0
- package/dist/data/test-runner/TestRunner.cjs.map +1 -0
- package/dist/data/test-runner/TestRunner.d.ts +42 -0
- package/dist/data/test-runner/TestRunner.d.ts.map +1 -0
- package/dist/data/test-runner/TestRunner.fixtures.d.ts +26 -0
- package/dist/data/test-runner/TestRunner.fixtures.d.ts.map +1 -0
- package/dist/data/test-runner/TestRunner.js +143 -0
- package/dist/data/test-runner/TestRunner.js.map +1 -0
- package/dist/data/test-runner/TestTree.cjs +42 -0
- package/dist/data/test-runner/TestTree.cjs.map +1 -0
- package/dist/data/test-runner/TestTree.d.ts +9 -0
- package/dist/data/test-runner/TestTree.d.ts.map +1 -0
- package/dist/data/test-runner/TestTree.js +42 -0
- package/dist/data/test-runner/TestTree.js.map +1 -0
- package/dist/data/test-runner/TestTreeNode.cjs +99 -0
- package/dist/data/test-runner/TestTreeNode.cjs.map +1 -0
- package/dist/data/test-runner/TestTreeNode.d.ts +12 -0
- package/dist/data/test-runner/TestTreeNode.d.ts.map +1 -0
- package/dist/data/test-runner/TestTreeNode.js +99 -0
- package/dist/data/test-runner/TestTreeNode.js.map +1 -0
- package/dist/data/test-runner/adapter.cjs +57 -0
- package/dist/data/test-runner/adapter.cjs.map +1 -0
- package/dist/data/test-runner/adapter.d.ts +60 -0
- package/dist/data/test-runner/adapter.d.ts.map +1 -0
- package/dist/data/test-runner/adapter.js +57 -0
- package/dist/data/test-runner/adapter.js.map +1 -0
- package/dist/data/test-runner/context.cjs +15 -0
- package/dist/data/test-runner/context.cjs.map +1 -0
- package/dist/data/test-runner/context.d.ts +40 -0
- package/dist/data/test-runner/context.d.ts.map +1 -0
- package/dist/data/test-runner/context.js +15 -0
- package/dist/data/test-runner/context.js.map +1 -0
- package/dist/data/test-runner/filterState.cjs +47 -0
- package/dist/data/test-runner/filterState.cjs.map +1 -0
- package/dist/data/test-runner/filterState.d.ts +14 -0
- package/dist/data/test-runner/filterState.d.ts.map +1 -0
- package/dist/data/test-runner/filterState.js +47 -0
- package/dist/data/test-runner/filterState.js.map +1 -0
- package/dist/data/test-runner/frameworkIcon.cjs +27 -0
- package/dist/data/test-runner/frameworkIcon.cjs.map +1 -0
- package/dist/data/test-runner/frameworkIcon.d.ts +3 -0
- package/dist/data/test-runner/frameworkIcon.d.ts.map +1 -0
- package/dist/data/test-runner/frameworkIcon.js +27 -0
- package/dist/data/test-runner/frameworkIcon.js.map +1 -0
- package/dist/data/test-runner/index.d.ts +14 -0
- package/dist/data/test-runner/index.d.ts.map +1 -0
- package/dist/data/test-runner/status.cjs +234 -0
- package/dist/data/test-runner/status.cjs.map +1 -0
- package/dist/data/test-runner/status.d.ts +52 -0
- package/dist/data/test-runner/status.d.ts.map +1 -0
- package/dist/data/test-runner/status.js +234 -0
- package/dist/data/test-runner/status.js.map +1 -0
- package/dist/data/test-runner/types.d.ts +148 -0
- package/dist/data/test-runner/types.d.ts.map +1 -0
- package/dist/data/version-info.cjs +26 -0
- package/dist/data/version-info.cjs.map +1 -0
- package/dist/data/version-info.d.ts +11 -0
- package/dist/data/version-info.d.ts.map +1 -0
- package/dist/data/version-info.js +26 -0
- package/dist/data/version-info.js.map +1 -0
- package/dist/data.cjs +49 -0
- package/dist/data.cjs.map +1 -1
- package/dist/data.d.ts +3 -0
- package/dist/data.d.ts.map +1 -1
- package/dist/data.js +50 -1
- package/dist/data.js.map +1 -1
- package/dist/index.cjs +59 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +60 -1
- package/dist/index.js.map +1 -1
- package/dist/overlay/DropdownMenu.cjs +57 -30
- package/dist/overlay/DropdownMenu.cjs.map +1 -1
- package/dist/overlay/DropdownMenu.d.ts.map +1 -1
- package/dist/overlay/DropdownMenu.js +57 -30
- package/dist/overlay/DropdownMenu.js.map +1 -1
- package/dist/overlay/Modal.cjs +62 -6
- package/dist/overlay/Modal.cjs.map +1 -1
- package/dist/overlay/Modal.d.ts +19 -1
- package/dist/overlay/Modal.d.ts.map +1 -1
- package/dist/overlay/Modal.js +62 -6
- package/dist/overlay/Modal.js.map +1 -1
- package/dist/rpc/CommandForm.cjs +2 -2
- package/dist/rpc/CommandForm.cjs.map +1 -1
- package/dist/rpc/CommandForm.d.ts.map +1 -1
- package/dist/rpc/CommandForm.js +3 -3
- package/dist/rpc/CommandForm.js.map +1 -1
- package/dist/rpc/formMetadata.cjs +5 -4
- package/dist/rpc/formMetadata.cjs.map +1 -1
- package/dist/rpc/formMetadata.d.ts.map +1 -1
- package/dist/rpc/formMetadata.js +6 -5
- package/dist/rpc/formMetadata.js.map +1 -1
- package/dist/rpc/types.cjs +4 -0
- package/dist/rpc/types.cjs.map +1 -1
- package/dist/rpc/types.d.ts +10 -0
- package/dist/rpc/types.d.ts.map +1 -1
- package/dist/rpc/types.js +5 -1
- package/dist/rpc/types.js.map +1 -1
- package/dist/styles.css +1 -1
- package/package.json +3 -1
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { jsx } from "react/jsx-runtime";
|
|
2
|
+
import { forwardRef } from "react";
|
|
3
|
+
import { cn } from "../lib/utils.js";
|
|
4
|
+
import { Icon } from "../data/Icon.js";
|
|
5
|
+
const IconButton = forwardRef(
|
|
6
|
+
({ icon, label, iconClassName, className, type = "button", ...props }, ref) => /* @__PURE__ */ jsx(
|
|
7
|
+
"button",
|
|
8
|
+
{
|
|
9
|
+
ref,
|
|
10
|
+
type,
|
|
11
|
+
"aria-label": label,
|
|
12
|
+
title: label,
|
|
13
|
+
className: cn(
|
|
14
|
+
"inline-flex shrink-0 items-center justify-center rounded-sm border-0 bg-transparent p-0 text-muted-foreground transition-colors hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50",
|
|
15
|
+
className
|
|
16
|
+
),
|
|
17
|
+
...props,
|
|
18
|
+
children: /* @__PURE__ */ jsx(Icon, { icon, className: cn("text-sm", iconClassName) })
|
|
19
|
+
}
|
|
20
|
+
)
|
|
21
|
+
);
|
|
22
|
+
IconButton.displayName = "IconButton";
|
|
23
|
+
export {
|
|
24
|
+
IconButton
|
|
25
|
+
};
|
|
26
|
+
//# sourceMappingURL=IconButton.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"IconButton.js","sources":["../../src/components/IconButton.tsx"],"sourcesContent":["import { forwardRef, type ButtonHTMLAttributes } from \"react\";\nimport { cn } from \"../lib/utils\";\nimport { Icon, type StaticIconComponent } from \"../data/Icon\";\n\nexport type IconButtonProps = ButtonHTMLAttributes<HTMLButtonElement> & {\n /** Imported icon component to render. */\n icon: StaticIconComponent;\n /** Accessible name; drives both aria-label and the tooltip title. */\n label: string;\n /** Extra classes for the glyph (e.g. size). */\n iconClassName?: string;\n};\n\n/**\n * A borderless, background-free icon button: the hover effect is on the glyph\n * itself (color), not a surrounding chip. Distinct from `Button` (which is a\n * box-shaped control). Icons render with `currentColor`, so `hover:text-*`\n * recolors the glyph.\n */\nexport const IconButton = forwardRef<HTMLButtonElement, IconButtonProps>(\n ({ icon, label, iconClassName, className, type = \"button\", ...props }, ref) => (\n <button\n ref={ref}\n type={type}\n aria-label={label}\n title={label}\n className={cn(\n \"inline-flex shrink-0 items-center justify-center rounded-sm border-0 bg-transparent p-0 text-muted-foreground transition-colors hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50\",\n className,\n )}\n {...props}\n >\n <Icon icon={icon} className={cn(\"text-sm\", iconClassName)} />\n </button>\n ),\n);\nIconButton.displayName = \"IconButton\";\n"],"names":[],"mappings":";;;;AAmBO,MAAM,aAAa;AAAA,EACxB,CAAC,EAAE,MAAM,OAAO,eAAe,WAAW,OAAO,UAAU,GAAG,SAAS,QACrE;AAAA,IAAC;AAAA,IAAA;AAAA,MACC;AAAA,MACA;AAAA,MACA,cAAY;AAAA,MACZ,OAAO;AAAA,MACP,WAAW;AAAA,QACT;AAAA,QACA;AAAA,MAAA;AAAA,MAED,GAAG;AAAA,MAEJ,8BAAC,MAAA,EAAK,MAAY,WAAW,GAAG,WAAW,aAAa,EAAA,CAAG;AAAA,IAAA;AAAA,EAAA;AAGjE;AACA,WAAW,cAAc;"}
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
3
|
+
const jsxRuntime = require("react/jsx-runtime");
|
|
4
|
+
const react = require("react");
|
|
5
|
+
const mi = require("@flanksource/icons/mi");
|
|
6
|
+
const utils = require("../lib/utils.cjs");
|
|
7
|
+
const Combobox = require("./Combobox.cjs");
|
|
8
|
+
const UiEdit = require("../icons/components/UiEdit.cjs");
|
|
9
|
+
function SecretKeySelector({
|
|
10
|
+
value,
|
|
11
|
+
onChange,
|
|
12
|
+
loadResources,
|
|
13
|
+
loadKeyPreview,
|
|
14
|
+
allowLiteral = false,
|
|
15
|
+
strict = false,
|
|
16
|
+
className
|
|
17
|
+
}) {
|
|
18
|
+
const source = (value == null ? void 0 : value.kind) ?? "secret";
|
|
19
|
+
const isLiteral = source === "value";
|
|
20
|
+
const refKind = isLiteral ? "secret" : source;
|
|
21
|
+
const selectedName = value && value.kind !== "value" ? value.name : "";
|
|
22
|
+
const selectedKey = value && value.kind !== "value" ? value.key : "";
|
|
23
|
+
const literalValue = value && value.kind === "value" ? value.value : "";
|
|
24
|
+
const [resources, setResources] = react.useState([]);
|
|
25
|
+
const [resourcesLoading, setResourcesLoading] = react.useState(false);
|
|
26
|
+
const [previews, setPreviews] = react.useState([]);
|
|
27
|
+
const [previewLoading, setPreviewLoading] = react.useState(false);
|
|
28
|
+
react.useEffect(() => {
|
|
29
|
+
if (isLiteral) return;
|
|
30
|
+
let cancelled = false;
|
|
31
|
+
setResourcesLoading(true);
|
|
32
|
+
loadResources(refKind).then((res) => !cancelled && setResources(res)).finally(() => !cancelled && setResourcesLoading(false));
|
|
33
|
+
return () => {
|
|
34
|
+
cancelled = true;
|
|
35
|
+
};
|
|
36
|
+
}, [loadResources, refKind, isLiteral]);
|
|
37
|
+
react.useEffect(() => {
|
|
38
|
+
if (!selectedName) {
|
|
39
|
+
setPreviews([]);
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
let cancelled = false;
|
|
43
|
+
setPreviewLoading(true);
|
|
44
|
+
loadKeyPreview(refKind, selectedName).then((res) => !cancelled && setPreviews(res)).catch(() => !cancelled && setPreviews([])).finally(() => !cancelled && setPreviewLoading(false));
|
|
45
|
+
return () => {
|
|
46
|
+
cancelled = true;
|
|
47
|
+
};
|
|
48
|
+
}, [loadKeyPreview, refKind, selectedName]);
|
|
49
|
+
const nameOptions = react.useMemo(
|
|
50
|
+
() => resources.map((r) => ({ value: r.name, label: r.name })),
|
|
51
|
+
[resources]
|
|
52
|
+
);
|
|
53
|
+
const selectedResource = react.useMemo(
|
|
54
|
+
() => resources.find((r) => r.name === selectedName),
|
|
55
|
+
[resources, selectedName]
|
|
56
|
+
);
|
|
57
|
+
const keyOptions = react.useMemo(
|
|
58
|
+
() => buildKeyOptions((selectedResource == null ? void 0 : selectedResource.keys) ?? [], previews, selectedKey),
|
|
59
|
+
[selectedResource, selectedKey, previews]
|
|
60
|
+
);
|
|
61
|
+
const nameInvalid = strict && !!selectedName && !resourcesLoading && !selectedResource;
|
|
62
|
+
const keyInvalid = strict && !!selectedKey && !!selectedResource && !resourcesLoading && !(selectedResource.keys ?? []).includes(selectedKey);
|
|
63
|
+
const setSource = (next) => onChange(next === "value" ? { kind: "value", value: "" } : { kind: next, name: "", key: "" });
|
|
64
|
+
const setName = (name) => onChange(name ? { kind: refKind, name, key: selectedKey } : void 0);
|
|
65
|
+
const setKey = (key) => selectedName ? onChange({ kind: refKind, name: selectedName, key }) : void 0;
|
|
66
|
+
const setLiteral = (v) => onChange({ kind: "value", value: v });
|
|
67
|
+
const sources = allowLiteral ? ["secret", "configmap", "value"] : ["secret", "configmap"];
|
|
68
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: utils.cn("flex items-center gap-2", className), children: [
|
|
69
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex shrink-0 overflow-hidden rounded border border-border text-xs", children: sources.map((k) => {
|
|
70
|
+
const Glyph = SOURCE_ICON[k];
|
|
71
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
72
|
+
"button",
|
|
73
|
+
{
|
|
74
|
+
type: "button",
|
|
75
|
+
onClick: () => setSource(k),
|
|
76
|
+
className: utils.cn(
|
|
77
|
+
"flex items-center gap-1 whitespace-nowrap px-2 py-1",
|
|
78
|
+
source === k ? "bg-primary text-primary-foreground" : "bg-muted text-muted-foreground hover:bg-accent"
|
|
79
|
+
),
|
|
80
|
+
children: [
|
|
81
|
+
/* @__PURE__ */ jsxRuntime.jsx(Glyph, { className: "h-3.5 w-3.5" }),
|
|
82
|
+
SOURCE_LABEL[k]
|
|
83
|
+
]
|
|
84
|
+
},
|
|
85
|
+
k
|
|
86
|
+
);
|
|
87
|
+
}) }),
|
|
88
|
+
isLiteral ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
89
|
+
"input",
|
|
90
|
+
{
|
|
91
|
+
type: "text",
|
|
92
|
+
value: literalValue,
|
|
93
|
+
onChange: (e) => setLiteral(e.target.value),
|
|
94
|
+
placeholder: "Static value…",
|
|
95
|
+
className: utils.cn(
|
|
96
|
+
"h-control-h min-w-0 flex-1 rounded border border-input bg-background px-control-px text-sm",
|
|
97
|
+
"outline-none focus-visible:ring-2 focus-visible:ring-ring"
|
|
98
|
+
)
|
|
99
|
+
}
|
|
100
|
+
) : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
101
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-44 shrink-0", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
102
|
+
Combobox.Combobox,
|
|
103
|
+
{
|
|
104
|
+
options: nameOptions,
|
|
105
|
+
value: selectedName,
|
|
106
|
+
onChange: setName,
|
|
107
|
+
allowCustomValue: true,
|
|
108
|
+
loading: resourcesLoading,
|
|
109
|
+
invalid: nameInvalid,
|
|
110
|
+
placeholder: `Select ${refKind}…`
|
|
111
|
+
}
|
|
112
|
+
) }),
|
|
113
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "min-w-0 flex-1", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
114
|
+
Combobox.Combobox,
|
|
115
|
+
{
|
|
116
|
+
options: keyOptions,
|
|
117
|
+
value: selectedKey,
|
|
118
|
+
onChange: setKey,
|
|
119
|
+
allowCustomValue: true,
|
|
120
|
+
loading: previewLoading,
|
|
121
|
+
invalid: keyInvalid,
|
|
122
|
+
placeholder: selectedName ? "Key…" : "—"
|
|
123
|
+
}
|
|
124
|
+
) })
|
|
125
|
+
] })
|
|
126
|
+
] });
|
|
127
|
+
}
|
|
128
|
+
const SOURCE_ICON = {
|
|
129
|
+
secret: mi.K8SSecret,
|
|
130
|
+
configmap: mi.K8SConfigmap,
|
|
131
|
+
value: UiEdit.UiEdit
|
|
132
|
+
};
|
|
133
|
+
const SOURCE_LABEL = {
|
|
134
|
+
secret: "Secret",
|
|
135
|
+
configmap: "ConfigMap",
|
|
136
|
+
value: "Value"
|
|
137
|
+
};
|
|
138
|
+
function buildKeyOptions(keys, previews, selectedKey) {
|
|
139
|
+
const masked = new Map(previews.map((p) => [p.key, p.value]));
|
|
140
|
+
const base = keys.map((k) => ({
|
|
141
|
+
value: k,
|
|
142
|
+
label: masked.has(k) ? `${k} — ${masked.get(k)}` : k
|
|
143
|
+
}));
|
|
144
|
+
if (selectedKey && !base.some((o) => o.value === selectedKey)) {
|
|
145
|
+
return [{ value: selectedKey, label: selectedKey }, ...base];
|
|
146
|
+
}
|
|
147
|
+
return base;
|
|
148
|
+
}
|
|
149
|
+
exports.SecretKeySelector = SecretKeySelector;
|
|
150
|
+
exports.buildKeyOptions = buildKeyOptions;
|
|
151
|
+
//# sourceMappingURL=SecretKeySelector.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SecretKeySelector.cjs","sources":["../../src/components/SecretKeySelector.tsx"],"sourcesContent":["import { useEffect, useMemo, useState, type ReactNode } from \"react\";\nimport { K8SSecret, K8SConfigmap } from \"@flanksource/icons/mi\";\nimport { cn } from \"../lib/utils\";\nimport { UiEdit } from \"../icons\";\nimport { Combobox, type ComboboxOption } from \"./Combobox\";\n\n// SecretKeySelector picks a Secret or ConfigMap and one of its keys, with a\n// mid-masked preview of every key's value so the operator can tell which key\n// holds the host vs the db vs a password. With `allowLiteral` it also offers a\n// \"Value\" mode for typing a static inline string. It is presentational: it\n// fetches nothing — the consumer supplies async `loadResources` /\n// `loadKeyPreview` getters. It emits a discriminated value the consumer lowers\n// into whatever reference shape it needs.\n\nexport type SecretKind = \"secret\" | \"configmap\";\n\n/** Toggle options: the two resource kinds plus the inline-literal mode. */\nexport type SecretValueSource = SecretKind | \"value\";\n\n/** One key's mid-masked preview. `value` is already masked by the consumer. */\nexport type KeyPreview = { key: string; value: string };\n\n/**\n * The selector's value: either a {kind,name,key} reference into a Secret /\n * ConfigMap, or a static inline literal ({kind:\"value\", value}).\n */\nexport type SecretKeyValue =\n | { kind: SecretKind; name: string; key: string }\n | { kind: \"value\"; value: string };\n\n/** A named secret/configmap and its data key names (values never returned). */\nexport type SecretResource = { name: string; keys?: string[] };\n\nexport type SecretKeySelectorProps = {\n value: SecretKeyValue | undefined;\n onChange: (next: SecretKeyValue | undefined) => void;\n /** Loads the named kind's resources (name + data key names). */\n loadResources: (kind: SecretKind) => Promise<SecretResource[]>;\n /** Loads mid-masked previews for the named resource's keys. */\n loadKeyPreview: (kind: SecretKind, name: string) => Promise<KeyPreview[]>;\n /** Offer a third \"Value\" toggle for typing a static inline literal. */\n allowLiteral?: boolean;\n /**\n * When true, a chosen secret/configmap name absent from the loaded resources,\n * or a key absent from the chosen resource's keys, is flagged invalid (once\n * the respective load settles). The reference doesn't exist in the namespace.\n * The literal \"Value\" mode is never strict-flagged.\n */\n strict?: boolean;\n className?: string;\n};\n\nexport function SecretKeySelector({\n value,\n onChange,\n loadResources,\n loadKeyPreview,\n allowLiteral = false,\n strict = false,\n className,\n}: SecretKeySelectorProps) {\n const source: SecretValueSource = value?.kind ?? \"secret\";\n const isLiteral = source === \"value\";\n // A literal value carries no resource kind; the loaders default to \"secret\".\n const refKind: SecretKind = isLiteral ? \"secret\" : source;\n const selectedName = value && value.kind !== \"value\" ? value.name : \"\";\n const selectedKey = value && value.kind !== \"value\" ? value.key : \"\";\n const literalValue = value && value.kind === \"value\" ? value.value : \"\";\n\n const [resources, setResources] = useState<SecretResource[]>([]);\n const [resourcesLoading, setResourcesLoading] = useState(false);\n const [previews, setPreviews] = useState<KeyPreview[]>([]);\n const [previewLoading, setPreviewLoading] = useState(false);\n\n useEffect(() => {\n // Literal mode references no resource, so skip the list fetch entirely.\n if (isLiteral) return;\n let cancelled = false;\n setResourcesLoading(true);\n loadResources(refKind)\n .then((res) => !cancelled && setResources(res))\n .finally(() => !cancelled && setResourcesLoading(false));\n return () => {\n cancelled = true;\n };\n }, [loadResources, refKind, isLiteral]);\n\n useEffect(() => {\n if (!selectedName) {\n setPreviews([]);\n return;\n }\n let cancelled = false;\n setPreviewLoading(true);\n loadKeyPreview(refKind, selectedName)\n .then((res) => !cancelled && setPreviews(res))\n .catch(() => !cancelled && setPreviews([]))\n .finally(() => !cancelled && setPreviewLoading(false));\n return () => {\n cancelled = true;\n };\n }, [loadKeyPreview, refKind, selectedName]);\n\n const nameOptions = useMemo<ComboboxOption[]>(\n () => resources.map((r) => ({ value: r.name, label: r.name })),\n [resources],\n );\n const selectedResource = useMemo(\n () => resources.find((r) => r.name === selectedName),\n [resources, selectedName],\n );\n const keyOptions = useMemo(\n () => buildKeyOptions(selectedResource?.keys ?? [], previews, selectedKey),\n [selectedResource, selectedKey, previews],\n );\n\n // Strict validity: the name is invalid when it names no loaded resource; the\n // key is invalid when, with a resolved resource, it isn't one of that\n // resource's keys. The key list comes from the resource list (not previews),\n // so both are gated on resourcesLoading only.\n const nameInvalid = strict && !!selectedName && !resourcesLoading && !selectedResource;\n const keyInvalid =\n strict &&\n !!selectedKey &&\n !!selectedResource &&\n !resourcesLoading &&\n !(selectedResource.keys ?? []).includes(selectedKey);\n\n // Switching source resets the selection: a resource kind clears name+key; the\n // literal mode seeds an empty string (so onChange emits a value-kind value).\n const setSource = (next: SecretValueSource) =>\n onChange(next === \"value\" ? { kind: \"value\", value: \"\" } : { kind: next, name: \"\", key: \"\" });\n const setName = (name: string) =>\n onChange(name ? { kind: refKind, name, key: selectedKey } : undefined);\n const setKey = (key: string) =>\n selectedName ? onChange({ kind: refKind, name: selectedName, key }) : undefined;\n const setLiteral = (v: string) => onChange({ kind: \"value\", value: v });\n\n const sources: SecretValueSource[] = allowLiteral\n ? [\"secret\", \"configmap\", \"value\"]\n : [\"secret\", \"configmap\"];\n\n return (\n <div className={cn(\"flex items-center gap-2\", className)}>\n <div className=\"flex shrink-0 overflow-hidden rounded border border-border text-xs\">\n {sources.map((k) => {\n const Glyph = SOURCE_ICON[k];\n return (\n <button\n key={k}\n type=\"button\"\n onClick={() => setSource(k)}\n className={cn(\n \"flex items-center gap-1 whitespace-nowrap px-2 py-1\",\n source === k\n ? \"bg-primary text-primary-foreground\"\n : \"bg-muted text-muted-foreground hover:bg-accent\",\n )}\n >\n <Glyph className=\"h-3.5 w-3.5\" />\n {SOURCE_LABEL[k]}\n </button>\n );\n })}\n </div>\n {isLiteral ? (\n <input\n type=\"text\"\n value={literalValue}\n onChange={(e) => setLiteral(e.target.value)}\n placeholder=\"Static value…\"\n className={cn(\n \"h-control-h min-w-0 flex-1 rounded border border-input bg-background px-control-px text-sm\",\n \"outline-none focus-visible:ring-2 focus-visible:ring-ring\",\n )}\n />\n ) : (\n <>\n <div className=\"w-44 shrink-0\">\n <Combobox\n options={nameOptions}\n value={selectedName}\n onChange={setName}\n allowCustomValue\n loading={resourcesLoading}\n invalid={nameInvalid}\n placeholder={`Select ${refKind}…`}\n />\n </div>\n <div className=\"min-w-0 flex-1\">\n <Combobox\n options={keyOptions}\n value={selectedKey}\n onChange={setKey}\n allowCustomValue\n loading={previewLoading}\n invalid={keyInvalid}\n placeholder={selectedName ? \"Key…\" : \"—\"}\n />\n </div>\n </>\n )}\n </div>\n );\n}\n\n// Icons share a className-only call shape; the flanksource FCs carry extra\n// static metadata that doesn't unify with ComponentType, so type the slot as a\n// plain render function instead.\nconst SOURCE_ICON: Record<SecretValueSource, (p: { className?: string }) => ReactNode> = {\n secret: K8SSecret,\n configmap: K8SConfigmap,\n value: UiEdit,\n};\n\nconst SOURCE_LABEL: Record<SecretValueSource, string> = {\n secret: \"Secret\",\n configmap: \"ConfigMap\",\n value: \"Value\",\n};\n\n// buildKeyOptions labels each key with its mid-masked preview value when one is\n// available. A selected key absent from the list is pinned so the selection\n// stays visible.\nexport function buildKeyOptions(\n keys: string[],\n previews: KeyPreview[],\n selectedKey: string,\n): ComboboxOption[] {\n const masked = new Map(previews.map((p) => [p.key, p.value]));\n const base = keys.map((k) => ({\n value: k,\n label: masked.has(k) ? `${k} — ${masked.get(k)}` : k,\n }));\n if (selectedKey && !base.some((o) => o.value === selectedKey)) {\n return [{ value: selectedKey, label: selectedKey }, ...base];\n }\n return base;\n}\n"],"names":["useState","useEffect","useMemo","cn","jsx","jsxs","Fragment","Combobox","K8SSecret","K8SConfigmap","UiEdit"],"mappings":"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"}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { ComboboxOption } from './Combobox';
|
|
2
|
+
export type SecretKind = "secret" | "configmap";
|
|
3
|
+
/** Toggle options: the two resource kinds plus the inline-literal mode. */
|
|
4
|
+
export type SecretValueSource = SecretKind | "value";
|
|
5
|
+
/** One key's mid-masked preview. `value` is already masked by the consumer. */
|
|
6
|
+
export type KeyPreview = {
|
|
7
|
+
key: string;
|
|
8
|
+
value: string;
|
|
9
|
+
};
|
|
10
|
+
/**
|
|
11
|
+
* The selector's value: either a {kind,name,key} reference into a Secret /
|
|
12
|
+
* ConfigMap, or a static inline literal ({kind:"value", value}).
|
|
13
|
+
*/
|
|
14
|
+
export type SecretKeyValue = {
|
|
15
|
+
kind: SecretKind;
|
|
16
|
+
name: string;
|
|
17
|
+
key: string;
|
|
18
|
+
} | {
|
|
19
|
+
kind: "value";
|
|
20
|
+
value: string;
|
|
21
|
+
};
|
|
22
|
+
/** A named secret/configmap and its data key names (values never returned). */
|
|
23
|
+
export type SecretResource = {
|
|
24
|
+
name: string;
|
|
25
|
+
keys?: string[];
|
|
26
|
+
};
|
|
27
|
+
export type SecretKeySelectorProps = {
|
|
28
|
+
value: SecretKeyValue | undefined;
|
|
29
|
+
onChange: (next: SecretKeyValue | undefined) => void;
|
|
30
|
+
/** Loads the named kind's resources (name + data key names). */
|
|
31
|
+
loadResources: (kind: SecretKind) => Promise<SecretResource[]>;
|
|
32
|
+
/** Loads mid-masked previews for the named resource's keys. */
|
|
33
|
+
loadKeyPreview: (kind: SecretKind, name: string) => Promise<KeyPreview[]>;
|
|
34
|
+
/** Offer a third "Value" toggle for typing a static inline literal. */
|
|
35
|
+
allowLiteral?: boolean;
|
|
36
|
+
/**
|
|
37
|
+
* When true, a chosen secret/configmap name absent from the loaded resources,
|
|
38
|
+
* or a key absent from the chosen resource's keys, is flagged invalid (once
|
|
39
|
+
* the respective load settles). The reference doesn't exist in the namespace.
|
|
40
|
+
* The literal "Value" mode is never strict-flagged.
|
|
41
|
+
*/
|
|
42
|
+
strict?: boolean;
|
|
43
|
+
className?: string;
|
|
44
|
+
};
|
|
45
|
+
export declare function SecretKeySelector({ value, onChange, loadResources, loadKeyPreview, allowLiteral, strict, className, }: SecretKeySelectorProps): import("react/jsx-runtime").JSX.Element;
|
|
46
|
+
export declare function buildKeyOptions(keys: string[], previews: KeyPreview[], selectedKey: string): ComboboxOption[];
|
|
47
|
+
//# sourceMappingURL=SecretKeySelector.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SecretKeySelector.d.ts","sourceRoot":"","sources":["../../src/components/SecretKeySelector.tsx"],"names":[],"mappings":"AAIA,OAAO,EAAY,KAAK,cAAc,EAAE,MAAM,YAAY,CAAC;AAU3D,MAAM,MAAM,UAAU,GAAG,QAAQ,GAAG,WAAW,CAAC;AAEhD,2EAA2E;AAC3E,MAAM,MAAM,iBAAiB,GAAG,UAAU,GAAG,OAAO,CAAC;AAErD,+EAA+E;AAC/E,MAAM,MAAM,UAAU,GAAG;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC;AAExD;;;GAGG;AACH,MAAM,MAAM,cAAc,GACtB;IAAE,IAAI,EAAE,UAAU,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,GAC/C;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC;AAErC,+EAA+E;AAC/E,MAAM,MAAM,cAAc,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,EAAE,CAAA;CAAE,CAAC;AAE/D,MAAM,MAAM,sBAAsB,GAAG;IACnC,KAAK,EAAE,cAAc,GAAG,SAAS,CAAC;IAClC,QAAQ,EAAE,CAAC,IAAI,EAAE,cAAc,GAAG,SAAS,KAAK,IAAI,CAAC;IACrD,gEAAgE;IAChE,aAAa,EAAE,CAAC,IAAI,EAAE,UAAU,KAAK,OAAO,CAAC,cAAc,EAAE,CAAC,CAAC;IAC/D,+DAA+D;IAC/D,cAAc,EAAE,CAAC,IAAI,EAAE,UAAU,EAAE,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,UAAU,EAAE,CAAC,CAAC;IAC1E,uEAAuE;IACvE,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB;;;;;OAKG;IACH,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,CAAC;AAEF,wBAAgB,iBAAiB,CAAC,EAChC,KAAK,EACL,QAAQ,EACR,aAAa,EACb,cAAc,EACd,YAAoB,EACpB,MAAc,EACd,SAAS,GACV,EAAE,sBAAsB,2CAgJxB;AAoBD,wBAAgB,eAAe,CAC7B,IAAI,EAAE,MAAM,EAAE,EACd,QAAQ,EAAE,UAAU,EAAE,EACtB,WAAW,EAAE,MAAM,GAClB,cAAc,EAAE,CAUlB"}
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import { jsxs, jsx, Fragment } from "react/jsx-runtime";
|
|
2
|
+
import { useState, useEffect, useMemo } from "react";
|
|
3
|
+
import { K8SConfigmap, K8SSecret } from "@flanksource/icons/mi";
|
|
4
|
+
import { cn } from "../lib/utils.js";
|
|
5
|
+
import { Combobox } from "./Combobox.js";
|
|
6
|
+
import { UiEdit } from "../icons/components/UiEdit.js";
|
|
7
|
+
function SecretKeySelector({
|
|
8
|
+
value,
|
|
9
|
+
onChange,
|
|
10
|
+
loadResources,
|
|
11
|
+
loadKeyPreview,
|
|
12
|
+
allowLiteral = false,
|
|
13
|
+
strict = false,
|
|
14
|
+
className
|
|
15
|
+
}) {
|
|
16
|
+
const source = (value == null ? void 0 : value.kind) ?? "secret";
|
|
17
|
+
const isLiteral = source === "value";
|
|
18
|
+
const refKind = isLiteral ? "secret" : source;
|
|
19
|
+
const selectedName = value && value.kind !== "value" ? value.name : "";
|
|
20
|
+
const selectedKey = value && value.kind !== "value" ? value.key : "";
|
|
21
|
+
const literalValue = value && value.kind === "value" ? value.value : "";
|
|
22
|
+
const [resources, setResources] = useState([]);
|
|
23
|
+
const [resourcesLoading, setResourcesLoading] = useState(false);
|
|
24
|
+
const [previews, setPreviews] = useState([]);
|
|
25
|
+
const [previewLoading, setPreviewLoading] = useState(false);
|
|
26
|
+
useEffect(() => {
|
|
27
|
+
if (isLiteral) return;
|
|
28
|
+
let cancelled = false;
|
|
29
|
+
setResourcesLoading(true);
|
|
30
|
+
loadResources(refKind).then((res) => !cancelled && setResources(res)).finally(() => !cancelled && setResourcesLoading(false));
|
|
31
|
+
return () => {
|
|
32
|
+
cancelled = true;
|
|
33
|
+
};
|
|
34
|
+
}, [loadResources, refKind, isLiteral]);
|
|
35
|
+
useEffect(() => {
|
|
36
|
+
if (!selectedName) {
|
|
37
|
+
setPreviews([]);
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
let cancelled = false;
|
|
41
|
+
setPreviewLoading(true);
|
|
42
|
+
loadKeyPreview(refKind, selectedName).then((res) => !cancelled && setPreviews(res)).catch(() => !cancelled && setPreviews([])).finally(() => !cancelled && setPreviewLoading(false));
|
|
43
|
+
return () => {
|
|
44
|
+
cancelled = true;
|
|
45
|
+
};
|
|
46
|
+
}, [loadKeyPreview, refKind, selectedName]);
|
|
47
|
+
const nameOptions = useMemo(
|
|
48
|
+
() => resources.map((r) => ({ value: r.name, label: r.name })),
|
|
49
|
+
[resources]
|
|
50
|
+
);
|
|
51
|
+
const selectedResource = useMemo(
|
|
52
|
+
() => resources.find((r) => r.name === selectedName),
|
|
53
|
+
[resources, selectedName]
|
|
54
|
+
);
|
|
55
|
+
const keyOptions = useMemo(
|
|
56
|
+
() => buildKeyOptions((selectedResource == null ? void 0 : selectedResource.keys) ?? [], previews, selectedKey),
|
|
57
|
+
[selectedResource, selectedKey, previews]
|
|
58
|
+
);
|
|
59
|
+
const nameInvalid = strict && !!selectedName && !resourcesLoading && !selectedResource;
|
|
60
|
+
const keyInvalid = strict && !!selectedKey && !!selectedResource && !resourcesLoading && !(selectedResource.keys ?? []).includes(selectedKey);
|
|
61
|
+
const setSource = (next) => onChange(next === "value" ? { kind: "value", value: "" } : { kind: next, name: "", key: "" });
|
|
62
|
+
const setName = (name) => onChange(name ? { kind: refKind, name, key: selectedKey } : void 0);
|
|
63
|
+
const setKey = (key) => selectedName ? onChange({ kind: refKind, name: selectedName, key }) : void 0;
|
|
64
|
+
const setLiteral = (v) => onChange({ kind: "value", value: v });
|
|
65
|
+
const sources = allowLiteral ? ["secret", "configmap", "value"] : ["secret", "configmap"];
|
|
66
|
+
return /* @__PURE__ */ jsxs("div", { className: cn("flex items-center gap-2", className), children: [
|
|
67
|
+
/* @__PURE__ */ jsx("div", { className: "flex shrink-0 overflow-hidden rounded border border-border text-xs", children: sources.map((k) => {
|
|
68
|
+
const Glyph = SOURCE_ICON[k];
|
|
69
|
+
return /* @__PURE__ */ jsxs(
|
|
70
|
+
"button",
|
|
71
|
+
{
|
|
72
|
+
type: "button",
|
|
73
|
+
onClick: () => setSource(k),
|
|
74
|
+
className: cn(
|
|
75
|
+
"flex items-center gap-1 whitespace-nowrap px-2 py-1",
|
|
76
|
+
source === k ? "bg-primary text-primary-foreground" : "bg-muted text-muted-foreground hover:bg-accent"
|
|
77
|
+
),
|
|
78
|
+
children: [
|
|
79
|
+
/* @__PURE__ */ jsx(Glyph, { className: "h-3.5 w-3.5" }),
|
|
80
|
+
SOURCE_LABEL[k]
|
|
81
|
+
]
|
|
82
|
+
},
|
|
83
|
+
k
|
|
84
|
+
);
|
|
85
|
+
}) }),
|
|
86
|
+
isLiteral ? /* @__PURE__ */ jsx(
|
|
87
|
+
"input",
|
|
88
|
+
{
|
|
89
|
+
type: "text",
|
|
90
|
+
value: literalValue,
|
|
91
|
+
onChange: (e) => setLiteral(e.target.value),
|
|
92
|
+
placeholder: "Static value…",
|
|
93
|
+
className: cn(
|
|
94
|
+
"h-control-h min-w-0 flex-1 rounded border border-input bg-background px-control-px text-sm",
|
|
95
|
+
"outline-none focus-visible:ring-2 focus-visible:ring-ring"
|
|
96
|
+
)
|
|
97
|
+
}
|
|
98
|
+
) : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
99
|
+
/* @__PURE__ */ jsx("div", { className: "w-44 shrink-0", children: /* @__PURE__ */ jsx(
|
|
100
|
+
Combobox,
|
|
101
|
+
{
|
|
102
|
+
options: nameOptions,
|
|
103
|
+
value: selectedName,
|
|
104
|
+
onChange: setName,
|
|
105
|
+
allowCustomValue: true,
|
|
106
|
+
loading: resourcesLoading,
|
|
107
|
+
invalid: nameInvalid,
|
|
108
|
+
placeholder: `Select ${refKind}…`
|
|
109
|
+
}
|
|
110
|
+
) }),
|
|
111
|
+
/* @__PURE__ */ jsx("div", { className: "min-w-0 flex-1", children: /* @__PURE__ */ jsx(
|
|
112
|
+
Combobox,
|
|
113
|
+
{
|
|
114
|
+
options: keyOptions,
|
|
115
|
+
value: selectedKey,
|
|
116
|
+
onChange: setKey,
|
|
117
|
+
allowCustomValue: true,
|
|
118
|
+
loading: previewLoading,
|
|
119
|
+
invalid: keyInvalid,
|
|
120
|
+
placeholder: selectedName ? "Key…" : "—"
|
|
121
|
+
}
|
|
122
|
+
) })
|
|
123
|
+
] })
|
|
124
|
+
] });
|
|
125
|
+
}
|
|
126
|
+
const SOURCE_ICON = {
|
|
127
|
+
secret: K8SSecret,
|
|
128
|
+
configmap: K8SConfigmap,
|
|
129
|
+
value: UiEdit
|
|
130
|
+
};
|
|
131
|
+
const SOURCE_LABEL = {
|
|
132
|
+
secret: "Secret",
|
|
133
|
+
configmap: "ConfigMap",
|
|
134
|
+
value: "Value"
|
|
135
|
+
};
|
|
136
|
+
function buildKeyOptions(keys, previews, selectedKey) {
|
|
137
|
+
const masked = new Map(previews.map((p) => [p.key, p.value]));
|
|
138
|
+
const base = keys.map((k) => ({
|
|
139
|
+
value: k,
|
|
140
|
+
label: masked.has(k) ? `${k} — ${masked.get(k)}` : k
|
|
141
|
+
}));
|
|
142
|
+
if (selectedKey && !base.some((o) => o.value === selectedKey)) {
|
|
143
|
+
return [{ value: selectedKey, label: selectedKey }, ...base];
|
|
144
|
+
}
|
|
145
|
+
return base;
|
|
146
|
+
}
|
|
147
|
+
export {
|
|
148
|
+
SecretKeySelector,
|
|
149
|
+
buildKeyOptions
|
|
150
|
+
};
|
|
151
|
+
//# sourceMappingURL=SecretKeySelector.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SecretKeySelector.js","sources":["../../src/components/SecretKeySelector.tsx"],"sourcesContent":["import { useEffect, useMemo, useState, type ReactNode } from \"react\";\nimport { K8SSecret, K8SConfigmap } from \"@flanksource/icons/mi\";\nimport { cn } from \"../lib/utils\";\nimport { UiEdit } from \"../icons\";\nimport { Combobox, type ComboboxOption } from \"./Combobox\";\n\n// SecretKeySelector picks a Secret or ConfigMap and one of its keys, with a\n// mid-masked preview of every key's value so the operator can tell which key\n// holds the host vs the db vs a password. With `allowLiteral` it also offers a\n// \"Value\" mode for typing a static inline string. It is presentational: it\n// fetches nothing — the consumer supplies async `loadResources` /\n// `loadKeyPreview` getters. It emits a discriminated value the consumer lowers\n// into whatever reference shape it needs.\n\nexport type SecretKind = \"secret\" | \"configmap\";\n\n/** Toggle options: the two resource kinds plus the inline-literal mode. */\nexport type SecretValueSource = SecretKind | \"value\";\n\n/** One key's mid-masked preview. `value` is already masked by the consumer. */\nexport type KeyPreview = { key: string; value: string };\n\n/**\n * The selector's value: either a {kind,name,key} reference into a Secret /\n * ConfigMap, or a static inline literal ({kind:\"value\", value}).\n */\nexport type SecretKeyValue =\n | { kind: SecretKind; name: string; key: string }\n | { kind: \"value\"; value: string };\n\n/** A named secret/configmap and its data key names (values never returned). */\nexport type SecretResource = { name: string; keys?: string[] };\n\nexport type SecretKeySelectorProps = {\n value: SecretKeyValue | undefined;\n onChange: (next: SecretKeyValue | undefined) => void;\n /** Loads the named kind's resources (name + data key names). */\n loadResources: (kind: SecretKind) => Promise<SecretResource[]>;\n /** Loads mid-masked previews for the named resource's keys. */\n loadKeyPreview: (kind: SecretKind, name: string) => Promise<KeyPreview[]>;\n /** Offer a third \"Value\" toggle for typing a static inline literal. */\n allowLiteral?: boolean;\n /**\n * When true, a chosen secret/configmap name absent from the loaded resources,\n * or a key absent from the chosen resource's keys, is flagged invalid (once\n * the respective load settles). The reference doesn't exist in the namespace.\n * The literal \"Value\" mode is never strict-flagged.\n */\n strict?: boolean;\n className?: string;\n};\n\nexport function SecretKeySelector({\n value,\n onChange,\n loadResources,\n loadKeyPreview,\n allowLiteral = false,\n strict = false,\n className,\n}: SecretKeySelectorProps) {\n const source: SecretValueSource = value?.kind ?? \"secret\";\n const isLiteral = source === \"value\";\n // A literal value carries no resource kind; the loaders default to \"secret\".\n const refKind: SecretKind = isLiteral ? \"secret\" : source;\n const selectedName = value && value.kind !== \"value\" ? value.name : \"\";\n const selectedKey = value && value.kind !== \"value\" ? value.key : \"\";\n const literalValue = value && value.kind === \"value\" ? value.value : \"\";\n\n const [resources, setResources] = useState<SecretResource[]>([]);\n const [resourcesLoading, setResourcesLoading] = useState(false);\n const [previews, setPreviews] = useState<KeyPreview[]>([]);\n const [previewLoading, setPreviewLoading] = useState(false);\n\n useEffect(() => {\n // Literal mode references no resource, so skip the list fetch entirely.\n if (isLiteral) return;\n let cancelled = false;\n setResourcesLoading(true);\n loadResources(refKind)\n .then((res) => !cancelled && setResources(res))\n .finally(() => !cancelled && setResourcesLoading(false));\n return () => {\n cancelled = true;\n };\n }, [loadResources, refKind, isLiteral]);\n\n useEffect(() => {\n if (!selectedName) {\n setPreviews([]);\n return;\n }\n let cancelled = false;\n setPreviewLoading(true);\n loadKeyPreview(refKind, selectedName)\n .then((res) => !cancelled && setPreviews(res))\n .catch(() => !cancelled && setPreviews([]))\n .finally(() => !cancelled && setPreviewLoading(false));\n return () => {\n cancelled = true;\n };\n }, [loadKeyPreview, refKind, selectedName]);\n\n const nameOptions = useMemo<ComboboxOption[]>(\n () => resources.map((r) => ({ value: r.name, label: r.name })),\n [resources],\n );\n const selectedResource = useMemo(\n () => resources.find((r) => r.name === selectedName),\n [resources, selectedName],\n );\n const keyOptions = useMemo(\n () => buildKeyOptions(selectedResource?.keys ?? [], previews, selectedKey),\n [selectedResource, selectedKey, previews],\n );\n\n // Strict validity: the name is invalid when it names no loaded resource; the\n // key is invalid when, with a resolved resource, it isn't one of that\n // resource's keys. The key list comes from the resource list (not previews),\n // so both are gated on resourcesLoading only.\n const nameInvalid = strict && !!selectedName && !resourcesLoading && !selectedResource;\n const keyInvalid =\n strict &&\n !!selectedKey &&\n !!selectedResource &&\n !resourcesLoading &&\n !(selectedResource.keys ?? []).includes(selectedKey);\n\n // Switching source resets the selection: a resource kind clears name+key; the\n // literal mode seeds an empty string (so onChange emits a value-kind value).\n const setSource = (next: SecretValueSource) =>\n onChange(next === \"value\" ? { kind: \"value\", value: \"\" } : { kind: next, name: \"\", key: \"\" });\n const setName = (name: string) =>\n onChange(name ? { kind: refKind, name, key: selectedKey } : undefined);\n const setKey = (key: string) =>\n selectedName ? onChange({ kind: refKind, name: selectedName, key }) : undefined;\n const setLiteral = (v: string) => onChange({ kind: \"value\", value: v });\n\n const sources: SecretValueSource[] = allowLiteral\n ? [\"secret\", \"configmap\", \"value\"]\n : [\"secret\", \"configmap\"];\n\n return (\n <div className={cn(\"flex items-center gap-2\", className)}>\n <div className=\"flex shrink-0 overflow-hidden rounded border border-border text-xs\">\n {sources.map((k) => {\n const Glyph = SOURCE_ICON[k];\n return (\n <button\n key={k}\n type=\"button\"\n onClick={() => setSource(k)}\n className={cn(\n \"flex items-center gap-1 whitespace-nowrap px-2 py-1\",\n source === k\n ? \"bg-primary text-primary-foreground\"\n : \"bg-muted text-muted-foreground hover:bg-accent\",\n )}\n >\n <Glyph className=\"h-3.5 w-3.5\" />\n {SOURCE_LABEL[k]}\n </button>\n );\n })}\n </div>\n {isLiteral ? (\n <input\n type=\"text\"\n value={literalValue}\n onChange={(e) => setLiteral(e.target.value)}\n placeholder=\"Static value…\"\n className={cn(\n \"h-control-h min-w-0 flex-1 rounded border border-input bg-background px-control-px text-sm\",\n \"outline-none focus-visible:ring-2 focus-visible:ring-ring\",\n )}\n />\n ) : (\n <>\n <div className=\"w-44 shrink-0\">\n <Combobox\n options={nameOptions}\n value={selectedName}\n onChange={setName}\n allowCustomValue\n loading={resourcesLoading}\n invalid={nameInvalid}\n placeholder={`Select ${refKind}…`}\n />\n </div>\n <div className=\"min-w-0 flex-1\">\n <Combobox\n options={keyOptions}\n value={selectedKey}\n onChange={setKey}\n allowCustomValue\n loading={previewLoading}\n invalid={keyInvalid}\n placeholder={selectedName ? \"Key…\" : \"—\"}\n />\n </div>\n </>\n )}\n </div>\n );\n}\n\n// Icons share a className-only call shape; the flanksource FCs carry extra\n// static metadata that doesn't unify with ComponentType, so type the slot as a\n// plain render function instead.\nconst SOURCE_ICON: Record<SecretValueSource, (p: { className?: string }) => ReactNode> = {\n secret: K8SSecret,\n configmap: K8SConfigmap,\n value: UiEdit,\n};\n\nconst SOURCE_LABEL: Record<SecretValueSource, string> = {\n secret: \"Secret\",\n configmap: \"ConfigMap\",\n value: \"Value\",\n};\n\n// buildKeyOptions labels each key with its mid-masked preview value when one is\n// available. A selected key absent from the list is pinned so the selection\n// stays visible.\nexport function buildKeyOptions(\n keys: string[],\n previews: KeyPreview[],\n selectedKey: string,\n): ComboboxOption[] {\n const masked = new Map(previews.map((p) => [p.key, p.value]));\n const base = keys.map((k) => ({\n value: k,\n label: masked.has(k) ? `${k} — ${masked.get(k)}` : k,\n }));\n if (selectedKey && !base.some((o) => o.value === selectedKey)) {\n return [{ value: selectedKey, label: selectedKey }, ...base];\n }\n return base;\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
3
|
+
const jsxRuntime = require("react/jsx-runtime");
|
|
4
|
+
const react = require("react");
|
|
5
|
+
const mi = require("@flanksource/icons/mi");
|
|
6
|
+
const utils = require("../lib/utils.cjs");
|
|
7
|
+
const Combobox = require("./Combobox.cjs");
|
|
8
|
+
const WORKLOAD_META = {
|
|
9
|
+
service: { label: "Service", Icon: mi.K8SService },
|
|
10
|
+
ingress: { label: "Ingress", Icon: mi.K8SIngress },
|
|
11
|
+
deployment: { label: "Deployment", Icon: mi.K8SDeployment },
|
|
12
|
+
statefulset: { label: "StatefulSet", Icon: mi.K8SStatefulset }
|
|
13
|
+
};
|
|
14
|
+
const ALL_KINDS = ["service", "ingress", "deployment", "statefulset"];
|
|
15
|
+
function WorkloadPicker({
|
|
16
|
+
value,
|
|
17
|
+
onChange,
|
|
18
|
+
loadWorkloads,
|
|
19
|
+
namespace,
|
|
20
|
+
kinds = ALL_KINDS,
|
|
21
|
+
strict = false,
|
|
22
|
+
placeholder = "Select workload / service…",
|
|
23
|
+
className
|
|
24
|
+
}) {
|
|
25
|
+
const [byKind, setByKind] = react.useState({});
|
|
26
|
+
const [loading, setLoading] = react.useState(false);
|
|
27
|
+
const kindsKey = kinds.join(",");
|
|
28
|
+
react.useEffect(() => {
|
|
29
|
+
let cancelled = false;
|
|
30
|
+
setLoading(true);
|
|
31
|
+
loadWorkloads(kinds).then((res) => {
|
|
32
|
+
if (!cancelled) setByKind(res);
|
|
33
|
+
}).finally(() => {
|
|
34
|
+
if (!cancelled) setLoading(false);
|
|
35
|
+
});
|
|
36
|
+
return () => {
|
|
37
|
+
cancelled = true;
|
|
38
|
+
};
|
|
39
|
+
}, [loadWorkloads, kindsKey]);
|
|
40
|
+
const options = react.useMemo(
|
|
41
|
+
() => buildWorkloadOptions(namespace, byKind, kinds, value),
|
|
42
|
+
[namespace, byKind, kinds, value]
|
|
43
|
+
);
|
|
44
|
+
const invalid = react.useMemo(() => {
|
|
45
|
+
if (!strict || !value || loading) return false;
|
|
46
|
+
const { keys, names } = loadedWorkloads(namespace, byKind, kinds);
|
|
47
|
+
return !keys.has(value) && !names.has(value);
|
|
48
|
+
}, [strict, value, loading, namespace, byKind, kinds]);
|
|
49
|
+
const selectedKind = kindForValue(kinds, value);
|
|
50
|
+
const leadMeta = WORKLOAD_META[selectedKind];
|
|
51
|
+
const LeadIcon = leadMeta.Icon;
|
|
52
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: utils.cn("flex items-center gap-2", className), children: [
|
|
53
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
54
|
+
LeadIcon,
|
|
55
|
+
{
|
|
56
|
+
className: "h-4 w-4 shrink-0 text-muted-foreground",
|
|
57
|
+
title: leadMeta.label,
|
|
58
|
+
"aria-label": leadMeta.label
|
|
59
|
+
}
|
|
60
|
+
),
|
|
61
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "min-w-0 flex-1", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
62
|
+
Combobox.Combobox,
|
|
63
|
+
{
|
|
64
|
+
options,
|
|
65
|
+
value,
|
|
66
|
+
onChange,
|
|
67
|
+
allowCustomValue: true,
|
|
68
|
+
loading,
|
|
69
|
+
invalid,
|
|
70
|
+
placeholder
|
|
71
|
+
}
|
|
72
|
+
) })
|
|
73
|
+
] });
|
|
74
|
+
}
|
|
75
|
+
function ingressHost(kind, r) {
|
|
76
|
+
var _a;
|
|
77
|
+
return kind === "ingress" ? (_a = r.hosts) == null ? void 0 : _a[0] : void 0;
|
|
78
|
+
}
|
|
79
|
+
function workloadName(kind, r) {
|
|
80
|
+
return ingressHost(kind, r) ?? r.name;
|
|
81
|
+
}
|
|
82
|
+
const KNOWN_KINDS = /* @__PURE__ */ new Set(["service", "ingress", "deployment", "statefulset"]);
|
|
83
|
+
function workloadKey(namespace, kind, r) {
|
|
84
|
+
const name = workloadName(kind, r);
|
|
85
|
+
return namespace ? `${namespace}/${kind}/${name}` : `${kind}/${name}`;
|
|
86
|
+
}
|
|
87
|
+
function parseWorkloadKey(value) {
|
|
88
|
+
const parts = value.split("/");
|
|
89
|
+
const name = parts.at(-1);
|
|
90
|
+
const maybeKind = parts.at(-2);
|
|
91
|
+
if (name != null && maybeKind != null && KNOWN_KINDS.has(maybeKind)) {
|
|
92
|
+
const namespace = parts.slice(0, -2).join("/");
|
|
93
|
+
const parsed = { kind: maybeKind, name };
|
|
94
|
+
if (namespace) parsed.namespace = namespace;
|
|
95
|
+
return parsed;
|
|
96
|
+
}
|
|
97
|
+
return { name: value };
|
|
98
|
+
}
|
|
99
|
+
function loadedWorkloads(namespace, byKind, kinds) {
|
|
100
|
+
const keys = /* @__PURE__ */ new Set();
|
|
101
|
+
const names = /* @__PURE__ */ new Set();
|
|
102
|
+
for (const kind of kinds) {
|
|
103
|
+
for (const r of byKind[kind] ?? []) {
|
|
104
|
+
keys.add(workloadKey(namespace, kind, r));
|
|
105
|
+
names.add(workloadName(kind, r));
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
return { keys, names };
|
|
109
|
+
}
|
|
110
|
+
function kindForValue(kinds, value) {
|
|
111
|
+
const kind = value ? parseWorkloadKey(value).kind : void 0;
|
|
112
|
+
return kind ?? kinds[0] ?? "service";
|
|
113
|
+
}
|
|
114
|
+
function buildWorkloadOptions(namespace, byKind, kinds, value) {
|
|
115
|
+
const opts = [];
|
|
116
|
+
for (const kind of kinds) {
|
|
117
|
+
const meta = WORKLOAD_META[kind];
|
|
118
|
+
const Icon = meta.Icon;
|
|
119
|
+
for (const r of byKind[kind] ?? []) {
|
|
120
|
+
const host = ingressHost(kind, r);
|
|
121
|
+
opts.push({
|
|
122
|
+
value: workloadKey(namespace, kind, r),
|
|
123
|
+
// The label is the human name; an ingress pairs its host with the
|
|
124
|
+
// ingress name for context.
|
|
125
|
+
label: host ? `${host} (${r.name})` : r.name,
|
|
126
|
+
group: meta.label,
|
|
127
|
+
icon: /* @__PURE__ */ jsxRuntime.jsx(Icon, { className: "h-4 w-4" })
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
if (value && !opts.some((o) => o.value === value)) {
|
|
132
|
+
const parsed = parseWorkloadKey(value);
|
|
133
|
+
const Icon = WORKLOAD_META[parsed.kind ?? kinds[0] ?? "service"].Icon;
|
|
134
|
+
return [{ value, label: parsed.name, icon: /* @__PURE__ */ jsxRuntime.jsx(Icon, { className: "h-4 w-4" }) }, ...opts];
|
|
135
|
+
}
|
|
136
|
+
return opts;
|
|
137
|
+
}
|
|
138
|
+
exports.WorkloadPicker = WorkloadPicker;
|
|
139
|
+
exports.buildWorkloadOptions = buildWorkloadOptions;
|
|
140
|
+
exports.kindForValue = kindForValue;
|
|
141
|
+
exports.loadedWorkloads = loadedWorkloads;
|
|
142
|
+
exports.parseWorkloadKey = parseWorkloadKey;
|
|
143
|
+
exports.workloadKey = workloadKey;
|
|
144
|
+
//# sourceMappingURL=WorkloadPicker.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"WorkloadPicker.cjs","sources":["../../src/components/WorkloadPicker.tsx"],"sourcesContent":["import { useEffect, useMemo, useState, type ComponentType } from \"react\";\nimport {\n K8SService,\n K8SIngress,\n K8SDeployment,\n K8SStatefulset,\n} from \"@flanksource/icons/mi\";\nimport { cn } from \"../lib/utils\";\nimport { Combobox, type ComboboxOption } from \"./Combobox\";\n\n// WorkloadPicker selects a backing Kubernetes workload (Service / Ingress /\n// Deployment / StatefulSet) for an endpoint. Options from every kind are merged\n// into one Combobox, grouped by kind via the Combobox group-header support and\n// labelled with the kind's icon.\n//\n// The emitted value is a `[namespace/]kind/name` key (see workloadKey /\n// parseWorkloadKey) so two workloads of different kinds that share a name don't\n// collide. An ingress's name-part is its first host (the routable address),\n// labelled with the ingress name for context; every other kind uses its name.\n//\n// The component is presentational: it owns the kind→group/icon mapping and the\n// key encoding but fetches nothing — the consumer supplies an async\n// `loadWorkloads` getter.\n\nexport type WorkloadKind = \"service\" | \"ingress\" | \"deployment\" | \"statefulset\";\n\n/** One workload returned by the loader. Ingresses may carry rule hostnames. */\nexport type WorkloadResource = { name: string; hosts?: string[] };\n\ntype KindIconProps = { className?: string; title?: string; \"aria-label\"?: string };\ntype KindMeta = { label: string; Icon: ComponentType<KindIconProps> };\n\n// WORKLOAD_META maps each kind to its group header label and icon, in display\n// order. The order of the keys drives the order options/groups appear in.\nconst WORKLOAD_META: Record<WorkloadKind, KindMeta> = {\n service: { label: \"Service\", Icon: K8SService },\n ingress: { label: \"Ingress\", Icon: K8SIngress },\n deployment: { label: \"Deployment\", Icon: K8SDeployment },\n statefulset: { label: \"StatefulSet\", Icon: K8SStatefulset },\n};\n\nconst ALL_KINDS: WorkloadKind[] = [\"service\", \"ingress\", \"deployment\", \"statefulset\"];\n\nexport type WorkloadPickerProps = {\n /**\n * Controlled selected value — a `[namespace/]kind/name` key (see\n * {@link workloadKey} / {@link parseWorkloadKey}).\n */\n value: string;\n /** Called with the chosen `[namespace/]kind/name` key (or \"\" when cleared). */\n onChange: (value: string) => void;\n /**\n * Async getter the component calls to load the requested kinds' workloads.\n * Returns a map keyed by kind. The consumer owns fetching/caching.\n */\n loadWorkloads: (kinds: WorkloadKind[]) => Promise<Record<WorkloadKind, WorkloadResource[]>>;\n /**\n * Namespace the workloads live in. When set it prefixes the emitted key\n * (`namespace/kind/name`), so values stay distinct across namespaces.\n */\n namespace?: string;\n /** Kinds to offer, in display order. Defaults to all four. */\n kinds?: WorkloadKind[];\n /**\n * When true, a non-empty value that does not match any loaded workload (once\n * loading settles) is flagged invalid — the picked workload doesn't exist in\n * the namespace. The value is still shown (pinned) so the user can see and\n * fix it; freeform entry is not rejected, only marked.\n */\n strict?: boolean;\n placeholder?: string;\n className?: string;\n};\n\nexport function WorkloadPicker({\n value,\n onChange,\n loadWorkloads,\n namespace,\n kinds = ALL_KINDS,\n strict = false,\n placeholder = \"Select workload / service…\",\n className,\n}: WorkloadPickerProps) {\n const [byKind, setByKind] = useState<Partial<Record<WorkloadKind, WorkloadResource[]>>>({});\n const [loading, setLoading] = useState(false);\n\n const kindsKey = kinds.join(\",\");\n useEffect(() => {\n let cancelled = false;\n setLoading(true);\n loadWorkloads(kinds)\n .then((res) => {\n if (!cancelled) setByKind(res);\n })\n .finally(() => {\n if (!cancelled) setLoading(false);\n });\n return () => {\n cancelled = true;\n };\n // loadWorkloads is expected to be stable (memoized by the consumer); kindsKey\n // is the joined `kinds` so a changed selection reloads without depending on\n // the array's identity.\n }, [loadWorkloads, kindsKey]);\n\n const options = useMemo(\n () => buildWorkloadOptions(namespace, byKind, kinds, value),\n [namespace, byKind, kinds, value],\n );\n\n // In strict mode a non-empty value is invalid once loading settles unless it\n // matches a loaded workload — either by its full `[namespace/]kind/name` key\n // (what the picker emits) or by its bare name-part (what consumers that\n // persist only the resolved name, e.g. a Service name or ingress host, feed\n // back in). buildWorkloadOptions pins an unmatched value first, so test\n // membership against the loaded set, not `options`.\n const invalid = useMemo(() => {\n if (!strict || !value || loading) return false;\n const { keys, names } = loadedWorkloads(namespace, byKind, kinds);\n return !keys.has(value) && !names.has(value);\n }, [strict, value, loading, namespace, byKind, kinds]);\n\n // The lead icon reflects the selected workload's kind (read from the value's\n // key), falling back to the first offered kind when nothing is selected or the\n // value carries no recognised kind (e.g. freeform input).\n const selectedKind = kindForValue(kinds, value);\n const leadMeta = WORKLOAD_META[selectedKind];\n const LeadIcon = leadMeta.Icon;\n return (\n <div className={cn(\"flex items-center gap-2\", className)}>\n <LeadIcon\n className=\"h-4 w-4 shrink-0 text-muted-foreground\"\n title={leadMeta.label}\n aria-label={leadMeta.label}\n />\n <div className=\"min-w-0 flex-1\">\n <Combobox\n options={options}\n value={value}\n onChange={onChange}\n allowCustomValue\n loading={loading}\n invalid={invalid}\n placeholder={placeholder}\n />\n </div>\n </div>\n );\n}\n\n// ingressHost is the first host an ingress routes, or undefined for non-ingress\n// kinds and host-less ingresses. An ingress's name-part is its host.\nfunction ingressHost(kind: WorkloadKind, r: WorkloadResource): string | undefined {\n return kind === \"ingress\" ? r.hosts?.[0] : undefined;\n}\n\n// workloadName is the name-part of a resource's key: an ingress's first host\n// (the routable address callers want), else the resource's own name.\nfunction workloadName(kind: WorkloadKind, r: WorkloadResource): string {\n return ingressHost(kind, r) ?? r.name;\n}\n\n// ParsedWorkloadKey is the structured form of a `[namespace/]kind/name` value.\nexport type ParsedWorkloadKey = { namespace?: string; kind?: WorkloadKind; name: string };\n\nconst KNOWN_KINDS = new Set<string>([\"service\", \"ingress\", \"deployment\", \"statefulset\"]);\n\n// workloadKey is the value a resource contributes (what onChange emits): a\n// `[namespace/]kind/name` composite so two workloads of different kinds sharing\n// a name don't collide. namespace is omitted when not supplied.\nexport function workloadKey(\n namespace: string | undefined,\n kind: WorkloadKind,\n r: WorkloadResource,\n): string {\n const name = workloadName(kind, r);\n return namespace ? `${namespace}/${kind}/${name}` : `${kind}/${name}`;\n}\n\n// parseWorkloadKey splits a `[namespace/]kind/name` value back into its parts.\n// The trailing segment is always the name (a host may itself contain no slash);\n// the segment before it is the kind when it is a known workload kind, and any\n// remaining leading segment is the namespace. A bare value (no recognised\n// kind/name shape) is returned as just its name so freeform input survives.\nexport function parseWorkloadKey(value: string): ParsedWorkloadKey {\n const parts = value.split(\"/\");\n const name = parts.at(-1);\n const maybeKind = parts.at(-2);\n if (name != null && maybeKind != null && KNOWN_KINDS.has(maybeKind)) {\n const namespace = parts.slice(0, -2).join(\"/\");\n const parsed: ParsedWorkloadKey = { kind: maybeKind as WorkloadKind, name };\n if (namespace) parsed.namespace = namespace;\n return parsed;\n }\n return { name: value };\n}\n\n// loadedWorkloads collapses the loaded resources into the two membership sets a\n// strict picker validates a value against: `keys` are the full\n// `[namespace/]kind/name` keys the picker emits, `names` are the bare name-parts\n// (service name / ingress host) a consumer may persist instead. A value in\n// neither set names a workload that doesn't exist in the namespace.\nexport function loadedWorkloads(\n namespace: string | undefined,\n byKind: Partial<Record<WorkloadKind, WorkloadResource[]>>,\n kinds: WorkloadKind[],\n): { keys: Set<string>; names: Set<string> } {\n const keys = new Set<string>();\n const names = new Set<string>();\n for (const kind of kinds) {\n for (const r of byKind[kind] ?? []) {\n keys.add(workloadKey(namespace, kind, r));\n names.add(workloadName(kind, r));\n }\n }\n return { keys, names };\n}\n\n// kindForValue returns the kind encoded in the selected value, so the lead icon\n// can mirror it without consulting the loaded resources. Falls back to the first\n// offered kind when nothing is selected or the value carries no recognised kind.\nexport function kindForValue(kinds: WorkloadKind[], value: string): WorkloadKind {\n const kind = value ? parseWorkloadKey(value).kind : undefined;\n return kind ?? kinds[0] ?? \"service\";\n}\n\n// buildWorkloadOptions flattens the per-kind resources into grouped Combobox\n// options in kind order. Each option's value is its `[namespace/]kind/name` key\n// and its label the human name; a current value not present in any kind is\n// pinned first (labelled with just its name-part) so the selection stays visible.\nexport function buildWorkloadOptions(\n namespace: string | undefined,\n byKind: Partial<Record<WorkloadKind, WorkloadResource[]>>,\n kinds: WorkloadKind[],\n value: string,\n): ComboboxOption[] {\n const opts: ComboboxOption[] = [];\n for (const kind of kinds) {\n const meta = WORKLOAD_META[kind];\n const Icon = meta.Icon;\n for (const r of byKind[kind] ?? []) {\n const host = ingressHost(kind, r);\n opts.push({\n value: workloadKey(namespace, kind, r),\n // The label is the human name; an ingress pairs its host with the\n // ingress name for context.\n label: host ? `${host} (${r.name})` : r.name,\n group: meta.label,\n icon: <Icon className=\"h-4 w-4\" />,\n });\n }\n }\n if (value && !opts.some((o) => o.value === value)) {\n const parsed = parseWorkloadKey(value);\n const Icon = WORKLOAD_META[parsed.kind ?? kinds[0] ?? \"service\"].Icon;\n return [{ value, label: parsed.name, icon: <Icon className=\"h-4 w-4\" /> }, ...opts];\n }\n return opts;\n}\n"],"names":["K8SService","K8SIngress","K8SDeployment","K8SStatefulset","useState","useEffect","useMemo","cn","jsx","Combobox"],"mappings":"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"}
|