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

@ -76,6 +76,8 @@ import type {
PutRegistriesRegistryIdPathParameters,
} from './generated-orval/api.schemas';
import { customAxiosInstance } from './axios-mutator';
import {
GithubComOcdpClusterServiceInternalAdapterInputHttpDtoInstanceResponseLastOperation as GeneratedInstanceLastOperationEnum,
GithubComOcdpClusterServiceInternalAdapterInputHttpDtoInstanceResponseStatus as GeneratedInstanceStatusEnum,
@ -91,9 +93,46 @@ export type * from './generated-orval/api.schemas';
// ---------- Friendly type aliases ----------
export type AuthResponse = GeneratedAuthResponse;
export type RegisterBody = GeneratedRegisterRequest;
export type AdminCreateUserRequest = RegisterBody & {
role?: string;
workspaceId?: string;
namespace?: string;
defaultClusterId?: string;
quotaCpu?: string;
quotaMemory?: string;
quotaGpu?: string;
quotaGpuMemory?: string;
isActive?: boolean;
mustChangePassword?: boolean;
};
export type LoginBody = GeneratedLoginRequest;
export type RefreshTokenBody = GeneratedRefreshTokenRequest;
export type UserResponse = GeneratedUserResponse;
export type UserResponse = GeneratedUserResponse & {
role?: string;
workspaceId?: string;
workspaceName?: string;
namespace?: string;
defaultClusterId?: string;
quotaCpu?: string;
quotaMemory?: string;
quotaGpu?: string;
quotaGpuMemory?: string;
isActive?: boolean;
mustChangePassword?: boolean;
};
export type UpdateUserRequest = {
role?: string;
workspaceId?: string;
namespace?: string;
defaultClusterId?: string;
quotaCpu?: string;
quotaMemory?: string;
quotaGpu?: string;
quotaGpuMemory?: string;
isActive?: boolean;
mustChangePassword?: boolean;
};
export type ValuesYamlResponse = { valuesYaml: string };
export type ClusterResponse = GeneratedClusterResponse;
export type CreateClusterRequest = GeneratedCreateClusterRequest;
@ -108,6 +147,49 @@ export type InstanceResponse = GeneratedInstanceResponse;
export type CreateInstanceRequest = GeneratedCreateInstanceRequest;
export type UpdateInstanceRequest = GeneratedUpdateInstanceRequest;
export type InstanceEntry = GeneratedInstanceEntry;
export type InstanceDiagnosticsResponse = {
instanceName?: string;
namespace?: string;
collectedAt?: string;
pods?: Array<{
name?: string;
namespace?: string;
phase?: string;
nodeName?: string;
podIp?: string;
hostIp?: string;
restartCount?: number;
containers?: Array<{
name?: string;
image?: string;
ready?: boolean;
restartCount?: number;
state?: string;
reason?: string;
message?: string;
}>;
conditions?: Array<{ type?: string; status?: string; reason?: string; message?: string }>;
creationTimestamp?: string;
}>;
services?: Array<{
name?: string;
namespace?: string;
type?: string;
clusterIP?: string;
ports?: Array<{ name?: string; protocol?: string; port?: number; targetPort?: string; nodePort?: number }>;
}>;
events?: Array<{
type?: string;
reason?: string;
message?: string;
involvedKind?: string;
involvedName?: string;
count?: number;
firstTimestamp?: string;
lastTimestamp?: string;
}>;
logs?: Array<{ pod?: string; container?: string; tailLines?: number; log?: string; error?: string }>;
};
export const INSTANCE_STATUS = GeneratedInstanceStatusEnum;
export type InstanceStatus = NonNullable<InstanceResponse['status']>;
export const INSTANCE_LAST_OPERATION = GeneratedInstanceLastOperationEnum;
@ -134,6 +216,13 @@ export type NodeMetricsResponse = GeneratedNodeMetricsResponse;
export const login = postAuthLogin;
export const register = postAuthRegister;
export const refreshAuth = postAuthRefresh;
export const listUsers = () => customAxiosInstance<UserResponse[]>({ url: "/users", method: "GET" });
export const createUser = (data: AdminCreateUserRequest) =>
customAxiosInstance<UserResponse>({ url: "/users", method: "POST", data });
export const updateUser = (userId: string, data: UpdateUserRequest) =>
customAxiosInstance<UserResponse>({ url: `/users/${encodeURIComponent(userId)}`, method: "PUT", data });
export const deleteUser = (userId: string) =>
customAxiosInstance<void>({ url: `/users/${encodeURIComponent(userId)}`, method: "DELETE" });
export const listClusters = getClusters;
export const createCluster = postClusters;
@ -148,6 +237,15 @@ export const getInstance = getClustersClusterIdInstancesInstanceId;
export const updateInstance = putClustersClusterIdInstancesInstanceId;
export const deleteInstance = deleteClustersClusterIdInstancesInstanceId;
export const listInstanceEntries = getClustersClusterIdInstancesInstanceIdEntries;
export const getInstanceDiagnostics = (
params: { clusterId: string; instanceId: string },
options?: { tailLines?: number },
) =>
customAxiosInstance<InstanceDiagnosticsResponse>({
url: `/clusters/${encodeURIComponent(params.clusterId)}/instances/${encodeURIComponent(params.instanceId)}/diagnostics`,
method: "GET",
params: options?.tailLines ? { tailLines: options.tailLines } : undefined,
});
export const listRegistries = getRegistries;
export const createRegistry = postRegistries;
@ -156,7 +254,13 @@ export const updateRegistry = putRegistriesRegistryId;
export const deleteRegistry = deleteRegistriesRegistryId;
export const checkRegistryHealth = getRegistriesRegistryIdHealth;
export const listRepositories = getRegistriesRegistryIdRepositories;
export const listRepositories = (
params: GetRegistriesRegistryIdRepositoriesPathParameters,
options?: { artifactType?: 'chart' | 'all' },
) =>
getRegistriesRegistryIdRepositories(params, {
params: options?.artifactType ? { artifact_type: options.artifactType } : undefined,
});
type ListArtifactsRequestOptions = AxiosOptions<typeof getRegistriesRegistryIdRepositoriesRepositoryNameArtifacts>;
export const listArtifacts = (
@ -173,6 +277,11 @@ export const listArtifacts = (
export const getArtifact = getRegistriesRegistryIdRepositoriesRepositoryNameArtifactsReference;
export const getValuesSchema = getRegistriesRegistryIdRepositoriesRepositoryNameArtifactsReferenceValuesSchema;
export const getValuesYaml = (params: GetValuesSchemaPathParameters) =>
customAxiosInstance<ValuesYamlResponse>({
url: `/registries/${encodeURIComponent(params.registryId)}/repositories/${encodeURIComponent(params.repositoryName)}/artifacts/${encodeURIComponent(params.reference)}/values-yaml`,
method: "GET",
});
export const listClusterMonitoring = getMonitoringClusters;
export const getClusterMonitoring = getMonitoringClustersClusterId;