fix: scale replicas in response, K8s metrics client, quota precheck, auth tests

- Add GetMetrics method to MetricsClient interface and implement cluster metrics API
- Add QuotaPrecheck service for validating resource quotas before deployment
- Add auth DTO with role/permission models and auth handler tests
- Add instance diagnostics: mounted NFS volumes, labels, annotations in pod diagnostics
- Update workspace handler with GetWorkspace endpoint and shared-user list
- Fix monitoring handler to use correct service method name
- Add tail_lines fallback in instance handler for snake_case query params
- Update nginx config for SSE log streaming support (no buffering)
- Add comprehensive test coverage: auth_service_test, auth_handler_test,
  auth_dto_test, metrics_client_test, quota_precheck_test
- Update error messages for quota validation and instance operations
- ModifyModal: fix YAML lineWidth:0, modified keys summary, delta-only submit
- InstanceCard: correctly disable scale-minus when replicas <= 0
- SidebarLayout: add hover transition for sidebar items
- Update todo.md and lessons.md with latest fixes
This commit is contained in:
Ivan087
2026-05-20 16:56:29 +08:00
parent 8f90cf0f0d
commit 33ddaf97db
59 changed files with 4805 additions and 457 deletions

View File

@ -35,7 +35,6 @@ export const ModifyModal: React.FC<ModifyModalProps> = ({
const [valuesYaml, setValuesYaml] = useState("");
const [loading, setLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
const [originalValuesYaml, setOriginalValuesYaml] = useState("");
const [modifiedKeys, setModifiedKeys] = useState<string[]>([]);
// Values Diff support
@ -60,7 +59,6 @@ export const ModifyModal: React.FC<ModifyModalProps> = ({
if (data?.current && Object.keys(data.current).length > 0) {
const currentYaml = stringifyYaml(data.current, { lineWidth: 0 });
setValuesYaml(currentYaml);
setOriginalValuesYaml(currentYaml);
setDiffData({ current: data.current, defaults: data.defaults ?? {} });
}
} catch (err) {
@ -71,7 +69,6 @@ export const ModifyModal: React.FC<ModifyModalProps> = ({
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);
@ -143,29 +140,18 @@ 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) => {
e.preventDefault();
setLoading(true);
setError(null);
try {
const deltaValues = computeDeltaValues();
if (valuesYaml.trim()) {
parseValuesYaml(valuesYaml);
}
const payload: UpdateInstanceRequest = {
version: tag && tag !== instance.version ? tag : undefined,
description: description.trim() || undefined,
values: deltaValues,
valuesYaml: valuesYaml.trim() || undefined,
};
@ -268,7 +254,7 @@ export const ModifyModal: React.FC<ModifyModalProps> = ({
Configuration Values
</label>
<p className="text-xs text-slate-500">
Editing current deployed values. Only modified keys are sent to the server.
Editing current deployed values. The full YAML is submitted so nested chart values stay intact.
</p>
{modifiedKeys.length > 0 && (
<div className="flex flex-wrap items-center gap-1.5 text-xs">
@ -368,27 +354,6 @@ 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 parsed = parseYaml(source);
if (parsed == null) {