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:
@ -20,6 +20,7 @@ import { InstanceErrors, SuccessMessages, formatApiError } from "@/shared/utils"
|
||||
import { InstanceCard } from "../components/InstanceCard";
|
||||
import { ModifyModal } from "../components/ModifyModal";
|
||||
import { EntriesModal } from "../components/EntriesModal";
|
||||
import { DiagnosticsModal } from "../components/DiagnosticsModal";
|
||||
import { globalCache } from "@/shared/services/artifact-cache";
|
||||
|
||||
const AUTO_REFRESH_INTERVAL_MS = 30000;
|
||||
@ -47,6 +48,7 @@ const InstancesManagementPage: React.FC = () => {
|
||||
// Modals
|
||||
const [modifyInstance, setModifyInstance] = useState<Instance | null>(null);
|
||||
const [entriesInstance, setEntriesInstance] = useState<Instance | null>(null);
|
||||
const [diagnosticsInstance, setDiagnosticsInstance] = useState<Instance | null>(null);
|
||||
|
||||
// 核心数据加载函数 - 使用全局缓存
|
||||
const loadDataCore = useCallback(async (options: LoadDataOptions = {}) => {
|
||||
@ -225,6 +227,10 @@ const InstancesManagementPage: React.FC = () => {
|
||||
setEntriesInstance(instance);
|
||||
}, []);
|
||||
|
||||
const handleViewDiagnostics = useCallback((instance: Instance) => {
|
||||
setDiagnosticsInstance(instance);
|
||||
}, []);
|
||||
|
||||
const handleModifyConfirm = useCallback(async (
|
||||
clusterId: string,
|
||||
instanceId: string,
|
||||
@ -333,43 +339,40 @@ const InstancesManagementPage: React.FC = () => {
|
||||
<div className={`grid grid-cols-1 gap-5 mb-8 ${
|
||||
clusters.length > 1 ? 'md:grid-cols-3' : 'md:grid-cols-2'
|
||||
}`}>
|
||||
<div className="relative group overflow-hidden bg-gradient-to-br from-blue-900/40 via-blue-800/30 to-blue-900/40 border border-blue-500/30 rounded-xl p-6 hover:border-blue-400/50 hover:shadow-xl hover:shadow-blue-500/20 transition-all duration-300">
|
||||
<div className="absolute top-0 right-0 w-32 h-32 bg-blue-500/10 rounded-full blur-3xl group-hover:bg-blue-500/20 transition-all"></div>
|
||||
<div className="relative group overflow-hidden bg-white border border-blue-100 rounded-lg p-6 hover:border-blue-200 hover:shadow-md transition-all duration-300">
|
||||
<div className="relative flex items-center justify-between">
|
||||
<div>
|
||||
<p className="text-sm font-semibold text-blue-300 uppercase tracking-wider mb-2">Total Instances</p>
|
||||
<p className="text-4xl font-bold text-white">{totalInstances}</p>
|
||||
<p className="text-sm font-semibold text-blue-700 uppercase tracking-wider mb-2">Total Instances</p>
|
||||
<p className="text-4xl font-bold text-slate-900">{totalInstances}</p>
|
||||
</div>
|
||||
<div className="p-4 bg-blue-500/20 rounded-xl border border-blue-400/30 shadow-lg shadow-blue-500/30">
|
||||
<Package className="w-8 h-8 text-blue-400" />
|
||||
<div className="p-4 bg-blue-50 rounded-lg border border-blue-100">
|
||||
<Package className="w-8 h-8 text-blue-600" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="relative group overflow-hidden bg-gradient-to-br from-emerald-900/40 via-emerald-800/30 to-green-900/40 border border-emerald-500/30 rounded-xl p-6 hover:border-emerald-400/50 hover:shadow-xl hover:shadow-emerald-500/20 transition-all duration-300">
|
||||
<div className="absolute top-0 right-0 w-32 h-32 bg-emerald-500/10 rounded-full blur-3xl group-hover:bg-emerald-500/20 transition-all"></div>
|
||||
<div className="relative group overflow-hidden bg-white border border-emerald-100 rounded-lg p-6 hover:border-emerald-200 hover:shadow-md transition-all duration-300">
|
||||
<div className="relative flex items-center justify-between">
|
||||
<div>
|
||||
<p className="text-sm font-semibold text-emerald-300 uppercase tracking-wider mb-2">Clusters</p>
|
||||
<p className="text-4xl font-bold text-white">{clusters.length}</p>
|
||||
<p className="text-sm font-semibold text-emerald-700 uppercase tracking-wider mb-2">Clusters</p>
|
||||
<p className="text-4xl font-bold text-slate-900">{clusters.length}</p>
|
||||
</div>
|
||||
<div className="p-4 bg-emerald-500/20 rounded-xl border border-emerald-400/30 shadow-lg shadow-emerald-500/30">
|
||||
<Server className="w-8 h-8 text-emerald-400" />
|
||||
<div className="p-4 bg-emerald-50 rounded-lg border border-emerald-100">
|
||||
<Server className="w-8 h-8 text-emerald-600" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Only show Filtered when there are multiple clusters */}
|
||||
{clusters.length > 1 && (
|
||||
<div className="relative group overflow-hidden bg-gradient-to-br from-purple-900/40 via-purple-800/30 to-purple-900/40 border border-purple-500/30 rounded-xl p-6 hover:border-purple-400/50 hover:shadow-xl hover:shadow-purple-500/20 transition-all duration-300">
|
||||
<div className="absolute top-0 right-0 w-32 h-32 bg-purple-500/10 rounded-full blur-3xl group-hover:bg-purple-500/20 transition-all"></div>
|
||||
<div className="relative group overflow-hidden bg-white border border-violet-100 rounded-lg p-6 hover:border-violet-200 hover:shadow-md transition-all duration-300">
|
||||
<div className="relative flex items-center justify-between">
|
||||
<div>
|
||||
<p className="text-sm font-semibold text-purple-300 uppercase tracking-wider mb-2">Showing</p>
|
||||
<p className="text-4xl font-bold text-white">{filteredInstances.length}</p>
|
||||
<p className="text-sm font-semibold text-violet-700 uppercase tracking-wider mb-2">Showing</p>
|
||||
<p className="text-4xl font-bold text-slate-900">{filteredInstances.length}</p>
|
||||
</div>
|
||||
<div className="p-4 bg-purple-500/20 rounded-xl border border-purple-400/30 shadow-lg shadow-purple-500/30">
|
||||
<Boxes className="w-8 h-8 text-purple-400" />
|
||||
<div className="p-4 bg-violet-50 rounded-lg border border-violet-100">
|
||||
<Boxes className="w-8 h-8 text-violet-600" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -379,13 +382,13 @@ const InstancesManagementPage: React.FC = () => {
|
||||
|
||||
{/* Enhanced Filters */}
|
||||
{clusters.length > 1 && (
|
||||
<div className="mb-6 p-5 bg-gradient-to-r from-slate-800/50 via-slate-800/30 to-slate-800/50 border border-slate-700/50 rounded-xl">
|
||||
<div className="mb-6 p-5 bg-white border border-slate-200 rounded-lg shadow-sm">
|
||||
<div className="flex items-center gap-4">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="p-2 bg-gradient-to-br from-cyan-500/20 to-blue-500/20 rounded-lg border border-cyan-500/30">
|
||||
<Server className="w-5 h-5 text-cyan-400" />
|
||||
</div>
|
||||
<label className="text-sm font-semibold text-slate-300">
|
||||
<label className="text-sm font-semibold text-slate-700">
|
||||
Filter by Cluster:
|
||||
</label>
|
||||
</div>
|
||||
@ -441,10 +444,10 @@ const InstancesManagementPage: React.FC = () => {
|
||||
<Server className="w-5 h-5 text-emerald-400" />
|
||||
</div>
|
||||
<div>
|
||||
<h2 className="text-xl font-bold text-white">
|
||||
<h2 className="text-xl font-bold text-slate-900">
|
||||
{cluster.name || "Unnamed Cluster"}
|
||||
</h2>
|
||||
<p className="text-sm text-slate-400 mt-0.5">
|
||||
<p className="text-sm text-slate-500 mt-0.5">
|
||||
{instances.length} {instances.length === 1 ? 'instance' : 'instances'} running
|
||||
</p>
|
||||
</div>
|
||||
@ -458,6 +461,7 @@ const InstancesManagementPage: React.FC = () => {
|
||||
onTerminate={handleTerminate}
|
||||
onRefresh={handleRefresh}
|
||||
onViewEntries={handleViewEntries}
|
||||
onViewDiagnostics={handleViewDiagnostics}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
@ -474,6 +478,7 @@ const InstancesManagementPage: React.FC = () => {
|
||||
onTerminate={handleTerminate}
|
||||
onRefresh={handleRefresh}
|
||||
onViewEntries={handleViewEntries}
|
||||
onViewDiagnostics={handleViewDiagnostics}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
@ -497,6 +502,13 @@ const InstancesManagementPage: React.FC = () => {
|
||||
onClose={() => setEntriesInstance(null)}
|
||||
/>
|
||||
)}
|
||||
|
||||
{diagnosticsInstance && (
|
||||
<DiagnosticsModal
|
||||
instance={diagnosticsInstance}
|
||||
onClose={() => setDiagnosticsInstance(null)}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user