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 =
|
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())
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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)?;
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -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({
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user