fix: real K8s replicas in list API, full Helm values in modify YAML editor

- Add Replicas field to entity.Instance
- Add EnrichReplicas to InstanceService (batch K8s deployment query)
- convertInstanceResponse uses instance.Replicas instead of hardcoded 0
- ModifyModal: load full Helm values from values-diff API (Current deployed)
- Remove stale loadValuesDiff, use single useEffect for all data loading
- Fix YAML lineWidth:0 for no line wrapping
This commit is contained in:
Ivan087
2026-05-13 16:15:11 +08:00
parent 96d42ee3e1
commit b88fe24aab
4 changed files with 45 additions and 34 deletions

View File

@ -139,6 +139,9 @@ func (h *InstanceHandler) ListInstances(w http.ResponseWriter, r *http.Request)
return return
} }
// Enrich with running replicas from K8s
instances = h.instanceService.EnrichReplicas(r.Context(), clusterID, instances)
responses := make([]*dto.InstanceResponse, 0, len(instances)) responses := make([]*dto.InstanceResponse, 0, len(instances))
for _, instance := range instances { for _, instance := range instances {
responses = append(responses, convertInstanceResponse(instance, false)) responses = append(responses, convertInstanceResponse(instance, false))
@ -586,7 +589,7 @@ func convertInstanceResponse(instance *entity.Instance, includeValues bool) *dto
LastOperation: string(instance.LastOperation), LastOperation: string(instance.LastOperation),
LastError: instance.LastError, LastError: instance.LastError,
Revision: instance.Revision, Revision: instance.Revision,
Replicas: 0, Replicas: instance.Replicas,
AllowedActions: []string{"view", "update", "delete"}, AllowedActions: []string{"view", "update", "delete"},
CreatedAt: instance.CreatedAt.Format("2006-01-02T15:04:05Z07:00"), CreatedAt: instance.CreatedAt.Format("2006-01-02T15:04:05Z07:00"),
UpdatedAt: instance.UpdatedAt.Format("2006-01-02T15:04:05Z07:00"), UpdatedAt: instance.UpdatedAt.Format("2006-01-02T15:04:05Z07:00"),

View File

@ -53,6 +53,7 @@ type Instance struct {
Revision int // Helm Release Revision Revision int // Helm Release Revision
CreatedAt time.Time CreatedAt time.Time
UpdatedAt time.Time UpdatedAt time.Time
Replicas int // Running K8s replicas (enriched, not persisted)
} }
// NewInstance 创建新实例 // NewInstance 创建新实例

View File

@ -473,6 +473,24 @@ func (s *InstanceService) ScaleInstance(ctx context.Context, clusterID, instance
return instance, nil return instance, nil
} }
// EnrichReplicas 批量获取实例的 K8s 实际副本数并设置到 entity 上
func (s *InstanceService) EnrichReplicas(ctx context.Context, clusterID string, instances []*entity.Instance) []*entity.Instance {
if s.scaleClient == nil || len(instances) == 0 {
return instances
}
cluster, err := s.clusterRepo.GetByID(ctx, clusterID)
if err != nil {
return instances
}
for _, inst := range instances {
r, err := s.scaleClient.GetDeploymentReplicas(ctx, cluster, inst.Namespace, inst.Name)
if err == nil {
inst.Replicas = int(r)
}
}
return instances
}
// GetRunningReplicas returns the actual K8s deployment replicas count. // GetRunningReplicas returns the actual K8s deployment replicas count.
func (s *InstanceService) GetRunningReplicas(ctx context.Context, cluster *entity.Cluster, instance *entity.Instance) int { func (s *InstanceService) GetRunningReplicas(ctx context.Context, cluster *entity.Cluster, instance *entity.Instance) int {
if s.scaleClient == nil { if s.scaleClient == nil {

View File

@ -40,37 +40,47 @@ export const ModifyModal: React.FC<ModifyModalProps> = ({
// Values Diff support // Values Diff support
const [showDiff, setShowDiff] = useState(false); const [showDiff, setShowDiff] = useState(false);
const [loadingDiff, setLoadingDiff] = useState(false); const [loadingDiff] = useState(false);
const [diffData, setDiffData] = useState<{ const [diffData, setDiffData] = useState<{
current: Record<string, any>; current: Record<string, any>;
defaults: Record<string, any>; defaults: Record<string, any>;
} | null>(null); } | null>(null);
const [diffError, setDiffError] = useState<string | null>(null); const [diffError] = useState<string | null>(null);
// Fetch full instance detail (list excludes values) and load diff // Fetch full Helm values (via values-diff API) and instance detail
useEffect(() => { useEffect(() => {
setTag(instance.version || ""); setTag(instance.version || "");
setDescription(""); setDescription("");
const loadFullInstance = async () => { const loadData = async () => {
if (instance.clusterId && instance.id) { if (instance.clusterId && instance.id) {
// Load values diff first — gives us the full current Helm values
try { try {
const detail = await getInstance( const data = await getInstanceValuesDiff(instance.clusterId, instance.id);
{ clusterId: instance.clusterId, instanceId: instance.id }, if (data?.current && Object.keys(data.current).length > 0) {
); const currentYaml = stringifyYaml(data.current, { lineWidth: 0 });
if (detail.values && Object.keys(detail.values).length > 0) {
const currentYaml = stringifyYaml(detail.values, { lineWidth: 0 });
setValuesYaml(currentYaml); setValuesYaml(currentYaml);
setOriginalValuesYaml(currentYaml); setOriginalValuesYaml(currentYaml);
setDiffData({ current: data.current, defaults: data.defaults ?? {} });
} }
} catch (err) { } catch (err) {
console.error('[ModifyModal] Failed to fetch instance detail:', err); console.error('[ModifyModal] Failed to load values diff:', err);
// Fallback: try instance detail
try {
const detail = await getInstance({ clusterId: instance.clusterId, instanceId: instance.id });
if (detail.values && Object.keys(detail.values).length > 0) {
const y = stringifyYaml(detail.values, { lineWidth: 0 });
setValuesYaml(y);
setOriginalValuesYaml(y);
}
} catch (err2) {
console.error('[ModifyModal] Failed to load instance detail:', err2);
}
} }
} }
loadValuesDiff();
}; };
loadFullInstance(); loadData();
}, [instance]); }, [instance]);
// Recompute modified keys when valuesYaml or diffData changes // Recompute modified keys when valuesYaml or diffData changes
@ -98,24 +108,6 @@ export const ModifyModal: React.FC<ModifyModalProps> = ({
} catch { /* ignore parse errors */ } } catch { /* ignore parse errors */ }
}, [valuesYaml, diffData]); }, [valuesYaml, diffData]);
const loadValuesDiff = async () => {
if (!instance.clusterId || !instance.id) return;
setLoadingDiff(true);
setDiffError(null);
setDiffData(null);
try {
const data = await getInstanceValuesDiff(instance.clusterId, instance.id);
if (data && data.current && data.defaults) {
setDiffData({ current: data.current, defaults: data.defaults });
}
} catch (err) {
console.error("[ModifyModal] Failed to load values diff:", err);
setDiffError("Failed to load values diff");
} finally {
setLoadingDiff(false);
}
};
const applyDefaults = () => { const applyDefaults = () => {
if (!diffData?.defaults) return; if (!diffData?.defaults) return;
@ -303,9 +295,6 @@ export const ModifyModal: React.FC<ModifyModalProps> = ({
<button <button
type="button" type="button"
onClick={() => { onClick={() => {
if (!showDiff && !diffData) {
loadValuesDiff();
}
setShowDiff(!showDiff); setShowDiff(!showDiff);
}} }}
className="flex items-center gap-2 text-sm font-medium text-indigo-600 hover:text-indigo-700 transition-colors" className="flex items-center gap-2 text-sm font-medium text-indigo-600 hover:text-indigo-700 transition-colors"