fix: scale replicas in response, YAML lineWidth, delta values, modified keys
- Scale API now returns actual replicas in instance response - ModifyModal: fix YAML stringify line breaking (lineWidth: 0) - ModifyModal: show modified keys summary above YAML editor - ModifyModal: only send delta (user-modified) values to server - Add diffObjects helper for deep object comparison
This commit is contained in:
@ -393,8 +393,11 @@ func (h *InstanceHandler) ScaleInstance(w http.ResponseWriter, r *http.Request)
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
instResp := convertInstanceResponse(result, true)
|
||||||
|
instResp.Replicas = req.Replicas
|
||||||
|
|
||||||
respondJSON(w, http.StatusOK, dto.ScaleInstanceResponse{
|
respondJSON(w, http.StatusOK, dto.ScaleInstanceResponse{
|
||||||
Instance: convertInstanceResponse(result, true),
|
Instance: instResp,
|
||||||
Replicas: req.Replicas,
|
Replicas: req.Replicas,
|
||||||
Message: fmt.Sprintf("Scaled to %d replicas", req.Replicas),
|
Message: fmt.Sprintf("Scaled to %d replicas", req.Replicas),
|
||||||
})
|
})
|
||||||
|
|||||||
@ -35,6 +35,8 @@ export const ModifyModal: React.FC<ModifyModalProps> = ({
|
|||||||
const [valuesYaml, setValuesYaml] = useState("");
|
const [valuesYaml, setValuesYaml] = useState("");
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const [error, setError] = useState<string | null>(null);
|
const [error, setError] = useState<string | null>(null);
|
||||||
|
const [originalValuesYaml, setOriginalValuesYaml] = useState("");
|
||||||
|
const [modifiedKeys, setModifiedKeys] = useState<string[]>([]);
|
||||||
|
|
||||||
// Values Diff support
|
// Values Diff support
|
||||||
const [showDiff, setShowDiff] = useState(false);
|
const [showDiff, setShowDiff] = useState(false);
|
||||||
@ -56,9 +58,10 @@ export const ModifyModal: React.FC<ModifyModalProps> = ({
|
|||||||
const detail = await getInstance(
|
const detail = await getInstance(
|
||||||
{ clusterId: instance.clusterId, instanceId: instance.id },
|
{ clusterId: instance.clusterId, instanceId: instance.id },
|
||||||
);
|
);
|
||||||
// Parse and display existing values as YAML
|
|
||||||
if (detail.values && Object.keys(detail.values).length > 0) {
|
if (detail.values && Object.keys(detail.values).length > 0) {
|
||||||
setValuesYaml(stringifyYaml(detail.values));
|
const currentYaml = stringifyYaml(detail.values, { lineWidth: 0 });
|
||||||
|
setValuesYaml(currentYaml);
|
||||||
|
setOriginalValuesYaml(currentYaml);
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('[ModifyModal] Failed to fetch instance detail:', err);
|
console.error('[ModifyModal] Failed to fetch instance detail:', err);
|
||||||
@ -70,6 +73,31 @@ export const ModifyModal: React.FC<ModifyModalProps> = ({
|
|||||||
loadFullInstance();
|
loadFullInstance();
|
||||||
}, [instance]);
|
}, [instance]);
|
||||||
|
|
||||||
|
// Recompute modified keys when valuesYaml or diffData changes
|
||||||
|
useEffect(() => {
|
||||||
|
if (!diffData?.defaults || !valuesYaml) return;
|
||||||
|
try {
|
||||||
|
const current = parseYaml(valuesYaml);
|
||||||
|
const defaults = diffData.defaults;
|
||||||
|
const changed: string[] = [];
|
||||||
|
const walkKeys = (curr: any, def: any, prefix: string) => {
|
||||||
|
if (curr === null || curr === undefined) return;
|
||||||
|
if (typeof curr !== 'object') return;
|
||||||
|
for (const key of Object.keys(curr)) {
|
||||||
|
const fullKey = prefix ? `${prefix}.${key}` : key;
|
||||||
|
if (JSON.stringify(curr[key]) !== JSON.stringify(def?.[key])) {
|
||||||
|
changed.push(fullKey);
|
||||||
|
}
|
||||||
|
if (typeof curr[key] === 'object' && curr[key] !== null && !Array.isArray(curr[key])) {
|
||||||
|
walkKeys(curr[key], def?.[key] ?? {}, fullKey);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
walkKeys(current, defaults, '');
|
||||||
|
setModifiedKeys(changed);
|
||||||
|
} catch { /* ignore parse errors */ }
|
||||||
|
}, [valuesYaml, diffData]);
|
||||||
|
|
||||||
const loadValuesDiff = async () => {
|
const loadValuesDiff = async () => {
|
||||||
if (!instance.clusterId || !instance.id) return;
|
if (!instance.clusterId || !instance.id) return;
|
||||||
|
|
||||||
@ -91,7 +119,7 @@ export const ModifyModal: React.FC<ModifyModalProps> = ({
|
|||||||
|
|
||||||
const applyDefaults = () => {
|
const applyDefaults = () => {
|
||||||
if (!diffData?.defaults) return;
|
if (!diffData?.defaults) return;
|
||||||
setValuesYaml(stringifyYaml(diffData.defaults));
|
setValuesYaml(stringifyYaml(diffData.defaults, { lineWidth: 0 }));
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -123,16 +151,29 @@ export const ModifyModal: React.FC<ModifyModalProps> = ({
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const computeDeltaValues = (): Record<string, any> | undefined => {
|
||||||
|
if (!valuesYaml.trim()) return undefined;
|
||||||
|
try {
|
||||||
|
const current = parseYaml(valuesYaml);
|
||||||
|
if (!originalValuesYaml) return current; // no original to compare against
|
||||||
|
const original = parseYaml(originalValuesYaml);
|
||||||
|
return diffObjects(current, original);
|
||||||
|
} catch {
|
||||||
|
return parseValuesYaml(valuesYaml);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const handleSubmit = async (e: React.FormEvent) => {
|
const handleSubmit = async (e: React.FormEvent) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
setError(null);
|
setError(null);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
const deltaValues = computeDeltaValues();
|
||||||
const payload: UpdateInstanceRequest = {
|
const payload: UpdateInstanceRequest = {
|
||||||
version: tag && tag !== instance.version ? tag : undefined,
|
version: tag && tag !== instance.version ? tag : undefined,
|
||||||
description: description.trim() || undefined,
|
description: description.trim() || undefined,
|
||||||
values: valuesYaml.trim() ? parseValuesYaml(valuesYaml) : undefined,
|
values: deltaValues,
|
||||||
valuesYaml: valuesYaml.trim() || undefined,
|
valuesYaml: valuesYaml.trim() || undefined,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -235,8 +276,18 @@ export const ModifyModal: React.FC<ModifyModalProps> = ({
|
|||||||
Configuration Values
|
Configuration Values
|
||||||
</label>
|
</label>
|
||||||
<p className="text-xs text-slate-500">
|
<p className="text-xs text-slate-500">
|
||||||
Editing current deployed values. Changes are merged with existing release values (--reuse-values).
|
Editing current deployed values. Only modified keys are sent to the server.
|
||||||
</p>
|
</p>
|
||||||
|
{modifiedKeys.length > 0 && (
|
||||||
|
<div className="flex flex-wrap items-center gap-1.5 text-xs">
|
||||||
|
<span className="text-slate-500">Modified:</span>
|
||||||
|
{modifiedKeys.map((k) => (
|
||||||
|
<span key={k} className="px-1.5 py-0.5 rounded bg-amber-100 text-amber-700 font-mono font-medium">
|
||||||
|
{k}
|
||||||
|
</span>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
<Textarea
|
<Textarea
|
||||||
value={valuesYaml}
|
value={valuesYaml}
|
||||||
onChange={(e) => setValuesYaml(e.target.value)}
|
onChange={(e) => setValuesYaml(e.target.value)}
|
||||||
@ -328,6 +379,27 @@ export const ModifyModal: React.FC<ModifyModalProps> = ({
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/** Deep diff: returns only keys in `current` whose values differ from `original` */
|
||||||
|
const diffObjects = (current: Record<string, any>, original: Record<string, any>): Record<string, any> => {
|
||||||
|
const delta: Record<string, any> = {};
|
||||||
|
for (const key of Object.keys(current)) {
|
||||||
|
const cv = current[key];
|
||||||
|
const ov = original[key];
|
||||||
|
if (JSON.stringify(cv) !== JSON.stringify(ov)) {
|
||||||
|
if (typeof cv === 'object' && cv !== null && !Array.isArray(cv) &&
|
||||||
|
typeof ov === 'object' && ov !== null && !Array.isArray(ov)) {
|
||||||
|
const nested = diffObjects(cv, ov);
|
||||||
|
if (Object.keys(nested).length > 0) {
|
||||||
|
delta[key] = nested;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
delta[key] = cv;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return delta;
|
||||||
|
};
|
||||||
|
|
||||||
const parseValuesYaml = (source: string): Record<string, any> => {
|
const parseValuesYaml = (source: string): Record<string, any> => {
|
||||||
const parsed = parseYaml(source);
|
const parsed = parseYaml(source);
|
||||||
if (parsed == null) {
|
if (parsed == null) {
|
||||||
|
|||||||
Reference in New Issue
Block a user