fix(kube): use current-context for kubectl auth; fix SelectValue label display
Some checks failed
Test / rust-tests (pull_request) Successful in 14m31s
PR Review Automation / review (pull_request) Has been cancelled
Test / frontend-typecheck (pull_request) Successful in 1m35s
Test / frontend-tests (pull_request) Successful in 1m40s
Test / rust-fmt-check (pull_request) Successful in 11m1s
Test / rust-clippy (pull_request) Successful in 12m39s
Some checks failed
Test / rust-tests (pull_request) Successful in 14m31s
PR Review Automation / review (pull_request) Has been cancelled
Test / frontend-typecheck (pull_request) Successful in 1m35s
Test / frontend-tests (pull_request) Successful in 1m40s
Test / rust-fmt-check (pull_request) Successful in 11m1s
Test / rust-clippy (pull_request) Successful in 12m39s
## kubectl credentials still failing after --context fix Root cause: both extract_context() (kube.rs) and upload_kubeconfig() (shell.rs) ignored the kubeconfig's current-context field and always picked contexts[0] from the contexts array. If a kubeconfig has multiple contexts and current-context points to entry N>0, we silently used the wrong context — one that may have empty or expired credentials — causing the 401 "the server has asked for the client to provide credentials" error on every kubectl call. Fixes: - extract_context(): read current-context field first; fall back to contexts[0] only when current-context is absent or empty. - extract_current_context_name(): new helper in kubeconfig.rs using the same line-scanner approach as parse_kubeconfig_contexts (no extra dependencies). - upload_kubeconfig(): use current-context to select the matching context entry when storing context name in kubeconfig_files; falls back to first entry. NOTE: existing kubeconfig rows in the database have the old (wrong) context stored. Re-uploading kubeconfig files after deploying this build will fix them. ## Cluster dropdown still showing UUID Root cause: SelectValue rendered ctx.value (the raw UUID passed to SelectItem's value prop) instead of the display label (SelectItem's children). The custom Select component had no mechanism to mirror a selected item's children into the trigger area. Fix: Select now builds a value→label Map by walking the children tree at render time (collectLabels). The map is memoised on children. SelectValue reads the display label from the map; if found, shows the label; otherwise falls back to the raw value so existing behaviour is preserved for callers that don't need it.
This commit is contained in:
parent
e046605ae6
commit
a2cff014e9
@ -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