refactor: full-stack restructure with multi-tenancy, workspace management, and K8s diagnostics

- Add Workspace domain (entity, repository, service, handler, DTO)
- Add multi-tenant K8s client with tenant binding and quota management
- Add K8s diagnostics client (instance diagnostics)
- Add authorization middleware (authz package)
- Restructure frontend to feature-based architecture (features/)
- Add User Management page in configuration
- Add AccessDenied page and route guards
- Refactor shared components (form inputs, layout, UI)
- Update Tailwind config for new design system
- Add comprehensive documentation (docs/, tasks/, plans)
- Improve cluster service with better kubeconfig handling
- Add tests for crypto, config, helm client, tenant binding
This commit is contained in:
Ivan087
2026-05-12 16:15:14 +08:00
parent c5e51ed069
commit 7f238a3168
172 changed files with 15703 additions and 3162 deletions

View File

@ -5,6 +5,7 @@
*/
import React, { useState, useEffect } from "react";
import { Settings } from "lucide-react";
import { parse as parseYaml, stringify as stringifyYaml } from "yaml";
import type { InstanceResponse, UpdateInstanceRequest } from "@/api";
import { getValuesSchema } from "@/api";
import {
@ -13,7 +14,6 @@ import {
FormField,
Input,
Textarea,
Checkbox,
ErrorState,
LoadingState,
Badge,
@ -35,8 +35,6 @@ export const ModifyModal: React.FC<ModifyModalProps> = ({
const [tag, setTag] = useState("");
const [description, setDescription] = useState("");
const [valuesYaml, setValuesYaml] = useState("");
const [wait, setWait] = useState(true);
const [timeout, setTimeout_] = useState(300);
const [loading, setLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
@ -58,7 +56,7 @@ export const ModifyModal: React.FC<ModifyModalProps> = ({
? JSON.parse(instance.values)
: instance.values;
setFormValues(parsedValues);
setValuesYaml(typeof parsedValues === 'object' ? JSON.stringify(parsedValues, null, 2) : String(parsedValues));
setValuesYaml(typeof parsedValues === 'object' ? stringifyYaml(parsedValues) : String(parsedValues));
} catch (err) {
console.error('[ModifyModal] Failed to parse existing values:', err);
setValuesYaml(String(instance.values) || "");
@ -104,8 +102,7 @@ export const ModifyModal: React.FC<ModifyModalProps> = ({
const handleFormValuesChange = (values: Record<string, any>) => {
setFormValues(values);
// Also update YAML representation
setValuesYaml(JSON.stringify(values, null, 2));
setValuesYaml(stringifyYaml(values));
};
const handleSubmit = async (e: React.FormEvent) => {
@ -116,7 +113,9 @@ export const ModifyModal: React.FC<ModifyModalProps> = ({
try {
const payload: UpdateInstanceRequest = {
version: tag && tag !== instance.version ? tag : undefined,
values: valuesYaml.trim() ? JSON.parse(valuesYaml) : undefined,
description: description.trim() || undefined,
values: valuesYaml.trim() ? parseValuesYaml(valuesYaml) : undefined,
valuesYaml: valuesYaml.trim() || undefined,
};
if (!instance.clusterId || !instance.id) {
@ -128,8 +127,8 @@ export const ModifyModal: React.FC<ModifyModalProps> = ({
await onConfirm(instance.clusterId, instance.id, payload);
onClose();
} catch (err: unknown) {
if (err instanceof SyntaxError) {
setError("Invalid JSON/YAML values. Please fix the configuration.");
if (err instanceof Error && err.message.includes("YAML")) {
setError(err.message);
} else {
setError((err as Error).message || "Failed to modify instance");
}
@ -144,7 +143,7 @@ export const ModifyModal: React.FC<ModifyModalProps> = ({
onClose={onClose}
title={`Modify Instance - ${instance.name || "Unnamed"}`}
icon={Settings}
iconColor="text-blue-400"
iconColor="text-blue-600"
size="lg"
footer={
<>
@ -175,15 +174,15 @@ export const ModifyModal: React.FC<ModifyModalProps> = ({
)}
{/* Current Info */}
<div className="bg-gray-800/50 border border-gray-700 rounded-lg p-4 space-y-2">
<p className="text-sm text-gray-300">
<span className="font-medium text-white">Current Version:</span> {instance.version || "N/A"}
<div className="bg-slate-50 border border-slate-200 rounded-lg p-4 space-y-2">
<p className="text-sm text-slate-700">
<span className="font-medium text-slate-900">Current Version:</span> {instance.version || "N/A"}
</p>
<p className="text-sm text-gray-300">
<span className="font-medium text-white">Cluster:</span> {instance.clusterId || "N/A"}
<p className="text-sm text-slate-700">
<span className="font-medium text-slate-900">Cluster:</span> {instance.clusterId || "N/A"}
</p>
<p className="text-sm text-gray-300">
<span className="font-medium text-white">Repository:</span> {instance.repository || "N/A"}
<p className="text-sm text-slate-700">
<span className="font-medium text-slate-900">Repository:</span> {instance.repository || "N/A"}
</p>
</div>
@ -215,12 +214,13 @@ export const ModifyModal: React.FC<ModifyModalProps> = ({
{/* Values Configuration */}
<div className="space-y-3">
<div className="flex items-center justify-between">
<label className="block text-sm font-medium text-gray-200">
<label className="block text-sm font-medium text-slate-700">
Configuration Values
</label>
{valuesSchema?.properties && (
<div className="flex gap-2">
<button
type="button"
onClick={() => setInputMethod('form')}
className="cursor-pointer"
>
@ -232,6 +232,7 @@ export const ModifyModal: React.FC<ModifyModalProps> = ({
</Badge>
</button>
<button
type="button"
onClick={() => setInputMethod('yaml')}
className="cursor-pointer"
>
@ -265,25 +266,9 @@ export const ModifyModal: React.FC<ModifyModalProps> = ({
)}
</div>
{/* Options */}
<div className="space-y-3">
<Checkbox
id="wait"
checked={wait}
onChange={(e) => setWait(e.target.checked)}
label="Wait for all resources to be ready"
/>
<FormField label="Timeout (seconds)">
<Input
type="number"
value={timeout}
onChange={(e) => setTimeout_(parseInt(e.target.value) || 300)}
min={60}
max={3600}
/>
</FormField>
</div>
<p className="text-xs text-slate-500">
Update applies the selected chart version and values override. Resource readiness is tracked from the instance list after submit.
</p>
</form>
</Modal>
);
@ -324,3 +309,14 @@ const extractJsonSchema = (schemaResponse: unknown): JsonSchema | null => {
return null;
};
const parseValuesYaml = (source: string): Record<string, any> => {
const parsed = parseYaml(source);
if (parsed == null) {
return {};
}
if (typeof parsed !== "object" || Array.isArray(parsed)) {
throw new Error("Values YAML must be an object");
}
return parsed as Record<string, any>;
};