fix(kube): use current-context for kubectl auth; fix SelectValue label display #81

Merged
sarman merged 1 commits from fix/kube-select-label-and-context into master 2026-06-08 00:56:20 +00:00
4 changed files with 81 additions and 8 deletions

View File

@ -117,6 +117,16 @@ fn extract_context(content: &str) -> Result<String, String> {
let value: serde_yaml::Value = let value: serde_yaml::Value =
serde_yaml::from_str(content).map_err(|e| format!("Invalid kubeconfig YAML: {}", e))?; 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 let contexts = value
.get("contexts") .get("contexts")
.and_then(|c| c.as_sequence()) .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()); return Err("No contexts found in kubeconfig".to_string());
} }
let first_context = contexts[0].get("name").and_then(|n| n.as_str()); contexts[0]
first_context .get("name")
.and_then(|n| n.as_str())
.map(|s| s.to_string()) .map(|s| s.to_string())
.ok_or_else(|| "Context name not found".to_string()) .ok_or_else(|| "Context name not found".to_string())
} }

View File

@ -41,11 +41,24 @@ pub async fn upload_kubeconfig(
// Generate ID // Generate ID
let id = uuid::Uuid::now_v7().to_string(); 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 contexts = crate::shell::kubeconfig::parse_kubeconfig_contexts(&content)?;
let context = contexts
.first() let current_context_name = crate::shell::kubeconfig::extract_current_context_name(&content);
.ok_or_else(|| "No contexts found in kubeconfig".to_string())?;
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 // Encrypt content
let encrypted_content = crate::integrations::auth::encrypt_token(&content)?; let encrypted_content = crate::integrations::auth::encrypt_token(&content)?;

View File

@ -30,6 +30,26 @@ pub async fn auto_detect_kubeconfig(_state: &AppState) -> Result<(), String> {
Err("Kubeconfig auto-detection not yet implemented".to_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> { pub fn parse_kubeconfig_contexts(content: &str) -> Result<Vec<KubeconfigContext>, String> {
// Parse YAML kubeconfig file // Parse YAML kubeconfig file
// Simple string parsing to extract contexts and cluster URLs // Simple string parsing to extract contexts and cluster URLs

View File

@ -188,10 +188,29 @@ interface SelectContextValue {
onChange: (value: string) => void; onChange: (value: string) => void;
open: boolean; open: boolean;
setOpen: (open: boolean) => void; setOpen: (open: boolean) => void;
labelMap: Map<string, React.ReactNode>;
} }
const SelectContext = React.createContext<SelectContextValue | null>(null); 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 { interface SelectProps {
value?: string; value?: string;
onValueChange?: (value: string) => void; onValueChange?: (value: string) => void;
@ -207,8 +226,13 @@ export function Select({ value = "", onValueChange, children }: SelectProps) {
}, },
[onValueChange] [onValueChange]
); );
const labelMap = React.useMemo(() => {
const map = new Map<string, React.ReactNode>();
collectLabels(children, map);
return map;
}, [children]);
return ( return (
<SelectContext.Provider value={{ value, onChange, open, setOpen }}> <SelectContext.Provider value={{ value, onChange, open, setOpen, labelMap }}>
<div className="relative">{children}</div> <div className="relative">{children}</div>
</SelectContext.Provider> </SelectContext.Provider>
); );
@ -242,7 +266,12 @@ export function SelectTrigger({
export function SelectValue({ placeholder }: { placeholder?: string }) { export function SelectValue({ placeholder }: { placeholder?: string }) {
const ctx = React.useContext(SelectContext)!; 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({ export function SelectContent({