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:
@ -174,7 +174,7 @@ export const SearchableSelect: React.FC<SearchableSelectProps> = ({
|
||||
return createPortal(
|
||||
<div
|
||||
ref={dropdownRef}
|
||||
className="fixed bg-gray-800 border border-gray-700 rounded-lg shadow-2xl flex flex-col"
|
||||
className="fixed bg-white border border-slate-200 rounded-md shadow-2xl flex flex-col"
|
||||
style={{
|
||||
top: `${dropdownPosition.top}px`,
|
||||
left: `${dropdownPosition.left}px`,
|
||||
@ -185,9 +185,9 @@ export const SearchableSelect: React.FC<SearchableSelectProps> = ({
|
||||
role="dialog"
|
||||
>
|
||||
{/* Search Input */}
|
||||
<div className="p-2 border-b border-gray-700 bg-gray-800 flex-shrink-0">
|
||||
<div className="p-2 border-b border-slate-200 bg-white flex-shrink-0">
|
||||
<div className="relative">
|
||||
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 w-4 h-4 text-gray-400" />
|
||||
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 w-4 h-4 text-slate-400" />
|
||||
<input
|
||||
ref={inputRef}
|
||||
type="text"
|
||||
@ -195,7 +195,7 @@ export const SearchableSelect: React.FC<SearchableSelectProps> = ({
|
||||
onChange={(e) => setSearchTerm(e.target.value)}
|
||||
onKeyDown={handleKeyDown}
|
||||
placeholder="Type to search..."
|
||||
className="w-full pl-9 pr-3 py-2 bg-gray-900 border border-gray-700 text-white rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||
className="w-full pl-9 pr-3 py-2 bg-slate-50 border border-slate-300 text-slate-900 rounded-md text-sm focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||
autoFocus
|
||||
/>
|
||||
</div>
|
||||
@ -213,7 +213,7 @@ export const SearchableSelect: React.FC<SearchableSelectProps> = ({
|
||||
role="listbox"
|
||||
>
|
||||
{filteredOptions.length === 0 ? (
|
||||
<li className="px-3 py-2 text-gray-500 text-sm text-center">
|
||||
<li className="px-3 py-2 text-slate-500 text-sm text-center">
|
||||
No results found
|
||||
</li>
|
||||
) : (
|
||||
@ -221,12 +221,12 @@ export const SearchableSelect: React.FC<SearchableSelectProps> = ({
|
||||
<li
|
||||
key={option}
|
||||
onClick={() => handleSelect(option)}
|
||||
className={`px-4 py-3 text-sm cursor-pointer transition border-b border-gray-700/30 last:border-0 ${
|
||||
className={`px-4 py-3 text-sm cursor-pointer transition border-b border-slate-100 last:border-0 ${
|
||||
index === highlightedIndex
|
||||
? 'bg-blue-600 text-white'
|
||||
: value === option
|
||||
? 'bg-gray-700 text-white'
|
||||
: 'text-gray-300 hover:bg-gray-700'
|
||||
? 'bg-blue-50 text-blue-700'
|
||||
: 'text-slate-700 hover:bg-slate-50'
|
||||
}`}
|
||||
role="option"
|
||||
aria-selected={value === option}
|
||||
@ -241,7 +241,7 @@ export const SearchableSelect: React.FC<SearchableSelectProps> = ({
|
||||
|
||||
{/* Result Count - Always show for large lists */}
|
||||
{(searchTerm || options.length > 20) && (
|
||||
<div className="px-3 py-2 bg-gray-900/50 border-t border-gray-700 text-xs text-gray-500 flex items-center justify-between flex-shrink-0">
|
||||
<div className="px-3 py-2 bg-slate-50 border-t border-slate-200 text-xs text-slate-500 flex items-center justify-between flex-shrink-0">
|
||||
<span>
|
||||
{searchTerm
|
||||
? `${filteredOptions.length} of ${options.length} options`
|
||||
@ -249,7 +249,7 @@ export const SearchableSelect: React.FC<SearchableSelectProps> = ({
|
||||
}
|
||||
</span>
|
||||
{!searchTerm && options.length > 20 && (
|
||||
<span className="text-blue-400">💡 Type to search</span>
|
||||
<span className="text-blue-600">Type to search</span>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
@ -263,23 +263,23 @@ export const SearchableSelect: React.FC<SearchableSelectProps> = ({
|
||||
{/* Trigger Button/Input */}
|
||||
<div
|
||||
onClick={handleToggle}
|
||||
className="w-full px-3 py-2 bg-gray-800 border border-gray-700 text-white rounded-lg cursor-pointer hover:border-gray-600 transition flex items-center justify-between"
|
||||
className="w-full px-3 py-2 bg-white border border-slate-300 text-slate-900 rounded-md cursor-pointer hover:border-slate-400 transition flex items-center justify-between"
|
||||
>
|
||||
<span className={value ? 'text-white' : 'text-gray-500'}>
|
||||
<span className={value ? 'text-slate-900' : 'text-slate-400'}>
|
||||
{displayText || placeholder}
|
||||
</span>
|
||||
<div className="flex items-center gap-1">
|
||||
{value && (
|
||||
<button
|
||||
onClick={handleClear}
|
||||
className="p-0.5 hover:bg-gray-700 rounded transition"
|
||||
className="p-0.5 hover:bg-slate-100 rounded transition"
|
||||
type="button"
|
||||
>
|
||||
<X className="w-4 h-4 text-gray-400" />
|
||||
<X className="w-4 h-4 text-slate-400" />
|
||||
</button>
|
||||
)}
|
||||
<ChevronDown
|
||||
className={`w-4 h-4 text-gray-400 transition-transform ${
|
||||
className={`w-4 h-4 text-slate-400 transition-transform ${
|
||||
isOpen ? 'transform rotate-180' : ''
|
||||
}`}
|
||||
/>
|
||||
@ -303,4 +303,3 @@ export const SearchableSelect: React.FC<SearchableSelectProps> = ({
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user