fix(kube): use current-context for kubectl auth; fix SelectValue label display #81
@ -117,6 +117,16 @@ fn extract_context(content: &str) -> Result<String, String> {
|
||||
let value: serde_yaml::Value =
|
||||
serde_yaml::from_str(content).map_err(|e| format!("Invalid kubeconfig YAML: {}", e))?;
|
||||
|
||||
// Prefer current-context — this is what kubectl uses by default and what the
|
||||
// user intends when they upload their kubeconfig. Falling back to contexts[0]
|
||||
// picks the wrong entry when the file has multiple contexts.
|
||||
if let Some(current) = value.get("current-context").and_then(|c| c.as_str()) {
|
||||
if !current.is_empty() {
|
||||
return Ok(current.to_string());
|
||||
}
|
||||
}
|
||||
|
||||
// No current-context set — fall back to the first context in the list
|
||||
let contexts = value
|
||||
.get("contexts")
|
||||
.and_then(|c| c.as_sequence())
|
||||
@ -126,8 +136,9 @@ fn extract_context(content: &str) -> Result<String, String> {
|
||||
return Err("No contexts found in kubeconfig".to_string());
|
||||
}
|
||||
|
||||
let first_context = contexts[0].get("name").and_then(|n| n.as_str());
|
||||
first_context
|
||||
contexts[0]
|
||||
.get("name")
|
||||
.and_then(|n| n.as_str())
|
||||
.map(|s| s.to_string())
|
||||
.ok_or_else(|| "Context name not found".to_string())
|
||||
}
|
||||
|
||||
@ -41,11 +41,24 @@ pub async fn upload_kubeconfig(
|
||||
// Generate ID
|
||||
let id = uuid::Uuid::now_v7().to_string();
|
||||
|
||||
// Parse kubeconfig to extract context
|
||||
// Parse kubeconfig to extract context.
|
||||
// Use current-context to select the right entry — the user's kubeconfig may
|
||||
// have multiple contexts and current-context is the one kubectl defaults to.
|
||||
let contexts = crate::shell::kubeconfig::parse_kubeconfig_contexts(&content)?;
|
||||
let context = contexts
|
||||
.first()
|
||||
.ok_or_else(|| "No contexts found in kubeconfig".to_string())?;
|
||||
|
||||
let current_context_name = crate::shell::kubeconfig::extract_current_context_name(&content);
|
||||
|
||||
let context = if let Some(ref current) = current_context_name {
|
||||
contexts
|
||||
.iter()
|
||||
.find(|c| &c.name == current)
|
||||
.or_else(|| contexts.first())
|
||||
.ok_or_else(|| "No contexts found in kubeconfig".to_string())?
|
||||
} else {
|
||||
contexts
|
||||
.first()
|
||||
.ok_or_else(|| "No contexts found in kubeconfig".to_string())?
|
||||
};
|
||||
|
||||
// Encrypt content
|
||||
let encrypted_content = crate::integrations::auth::encrypt_token(&content)?;
|
||||
|
||||
@ -30,6 +30,26 @@ pub async fn auto_detect_kubeconfig(_state: &AppState) -> Result<(), String> {
|
||||
Err("Kubeconfig auto-detection not yet implemented".to_string())
|
||||
}
|
||||
|
||||
/// Return the `current-context` value from a kubeconfig YAML, if set.
|
||||
/// Uses simple line scanning so it stays consistent with `parse_kubeconfig_contexts`.
|
||||
pub fn extract_current_context_name(content: &str) -> Option<String> {
|
||||
for line in content.lines() {
|
||||
let trimmed = line.trim();
|
||||
if trimmed.starts_with("current-context:") {
|
||||
let val = trimmed
|
||||
.trim_start_matches("current-context:")
|
||||
.trim()
|
||||
.trim_matches('"')
|
||||
.trim_matches('\'')
|
||||
.to_string();
|
||||
if !val.is_empty() {
|
||||
return Some(val);
|
||||
}
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
pub fn parse_kubeconfig_contexts(content: &str) -> Result<Vec<KubeconfigContext>, String> {
|
||||
// Parse YAML kubeconfig file
|
||||
// Simple string parsing to extract contexts and cluster URLs
|
||||
|
||||
@ -188,10 +188,29 @@ interface SelectContextValue {
|
||||
onChange: (value: string) => void;
|
||||
open: boolean;
|
||||
setOpen: (open: boolean) => void;
|
||||
labelMap: Map<string, React.ReactNode>;
|
||||
}
|
||||
|
||||
const SelectContext = React.createContext<SelectContextValue | null>(null);
|
||||
|
||||
/** Walk a React node tree collecting SelectItem value→children mappings. */
|
||||
function collectLabels(
|
||||
nodes: React.ReactNode,
|
||||
map: Map<string, React.ReactNode>
|
||||
): void {
|
||||
React.Children.forEach(nodes, (child) => {
|
||||
if (!React.isValidElement(child)) return;
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const props = child.props as any;
|
||||
if (child.type === SelectItem && typeof props.value === "string") {
|
||||
map.set(props.value, props.children);
|
||||
}
|
||||
if (props.children) {
|
||||
collectLabels(props.children as React.ReactNode, map);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
interface SelectProps {
|
||||
value?: string;
|
||||
onValueChange?: (value: string) => void;
|
||||
@ -207,8 +226,13 @@ export function Select({ value = "", onValueChange, children }: SelectProps) {
|
||||
},
|
||||
[onValueChange]
|
||||
);
|
||||
const labelMap = React.useMemo(() => {
|
||||
const map = new Map<string, React.ReactNode>();
|
||||
collectLabels(children, map);
|
||||
return map;
|
||||
}, [children]);
|
||||
return (
|
||||
<SelectContext.Provider value={{ value, onChange, open, setOpen }}>
|
||||
<SelectContext.Provider value={{ value, onChange, open, setOpen, labelMap }}>
|
||||
<div className="relative">{children}</div>
|
||||
</SelectContext.Provider>
|
||||
);
|
||||
@ -242,7 +266,12 @@ export function SelectTrigger({
|
||||
|
||||
export function SelectValue({ placeholder }: { placeholder?: string }) {
|
||||
const ctx = React.useContext(SelectContext)!;
|
||||
return <span className={ctx.value ? "text-foreground" : "text-muted-foreground"}>{ctx.value || placeholder}</span>;
|
||||
const label = ctx.value ? ctx.labelMap.get(ctx.value) : undefined;
|
||||
return (
|
||||
<span className={ctx.value ? "text-foreground" : "text-muted-foreground"}>
|
||||
{label ?? (ctx.value ? ctx.value : placeholder)}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
export function SelectContent({
|
||||
|
||||
Loading…
Reference in New Issue
Block a user