Refactor NewModuleModal to comment out health_status field and improve code readability. Update TenantDetails to fetch modules based on tenant ID and adjust ModulesTab to handle module data more effectively. Modify module service to accept tenant ID for fetching modules. Clean up unused code in RolesTable and UsersTable components by removing header text.
This commit is contained in:
parent
dd71820ac9
commit
c656594154
@ -49,7 +49,7 @@ const newModuleSchema = z.object({
|
|||||||
min_replicas: z.number().int().min(1, 'min_replicas must be at least 1').max(50, 'min_replicas must be at most 50').optional().nullable(),
|
min_replicas: z.number().int().min(1, 'min_replicas must be at least 1').max(50, 'min_replicas must be at most 50').optional().nullable(),
|
||||||
max_replicas: z.number().int().min(1, 'max_replicas must be at least 1').max(50, 'max_replicas must be at most 50').optional().nullable(),
|
max_replicas: z.number().int().min(1, 'max_replicas must be at least 1').max(50, 'max_replicas must be at most 50').optional().nullable(),
|
||||||
last_health_check: z.string().optional().nullable(),
|
last_health_check: z.string().optional().nullable(),
|
||||||
health_status: z.string().max(20, 'health_status must be at most 20 characters').optional().nullable(),
|
// health_status: z.string().max(20, 'health_status must be at most 20 characters').optional().nullable(),
|
||||||
consecutive_failures: z.number().int().optional().nullable(),
|
consecutive_failures: z.number().int().optional().nullable(),
|
||||||
registered_by: z.string().uuid().optional().nullable(),
|
registered_by: z.string().uuid().optional().nullable(),
|
||||||
tenant_id: z.string().uuid().optional().nullable(),
|
tenant_id: z.string().uuid().optional().nullable(),
|
||||||
@ -96,7 +96,7 @@ export const NewModuleModal = ({
|
|||||||
min_replicas: null,
|
min_replicas: null,
|
||||||
max_replicas: null,
|
max_replicas: null,
|
||||||
last_health_check: null,
|
last_health_check: null,
|
||||||
health_status: null,
|
// health_status: null,
|
||||||
consecutive_failures: null,
|
consecutive_failures: null,
|
||||||
registered_by: null,
|
registered_by: null,
|
||||||
tenant_id: null,
|
tenant_id: null,
|
||||||
@ -126,7 +126,7 @@ export const NewModuleModal = ({
|
|||||||
min_replicas: null,
|
min_replicas: null,
|
||||||
max_replicas: null,
|
max_replicas: null,
|
||||||
last_health_check: null,
|
last_health_check: null,
|
||||||
health_status: null,
|
// health_status: null,
|
||||||
consecutive_failures: null,
|
consecutive_failures: null,
|
||||||
registered_by: null,
|
registered_by: null,
|
||||||
tenant_id: null,
|
tenant_id: null,
|
||||||
@ -274,7 +274,7 @@ export const NewModuleModal = ({
|
|||||||
<p className="text-sm text-[#ef4444]">{errors.root.message}</p>
|
<p className="text-sm text-[#ef4444]">{errors.root.message}</p>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
{!apiKey && (
|
||||||
<div className="flex flex-col gap-0">
|
<div className="flex flex-col gap-0">
|
||||||
{/* Basic Information Section */}
|
{/* Basic Information Section */}
|
||||||
<div className="mb-4">
|
<div className="mb-4">
|
||||||
@ -475,6 +475,7 @@ export const NewModuleModal = ({
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
)}
|
||||||
</form>
|
</form>
|
||||||
</Modal >
|
</Modal >
|
||||||
);
|
);
|
||||||
|
|||||||
@ -288,7 +288,7 @@ export const RolesTable = ({ tenantId, showHeader = true, compact = false }: Rol
|
|||||||
<>
|
<>
|
||||||
<div className="flex flex-col gap-4">
|
<div className="flex flex-col gap-4">
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<h3 className="text-lg font-semibold text-[#0f1724]">Roles</h3>
|
<h3 className="text-lg font-semibold text-[#0f1724]"></h3>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<FilterDropdown
|
<FilterDropdown
|
||||||
label="Scope"
|
label="Scope"
|
||||||
|
|||||||
@ -330,7 +330,7 @@ export const UsersTable = ({ tenantId, showHeader = true, compact = false }: Use
|
|||||||
<>
|
<>
|
||||||
<div className="flex flex-col gap-4">
|
<div className="flex flex-col gap-4">
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<h3 className="text-lg font-semibold text-[#0f1724]">Users</h3>
|
<h3 className="text-lg font-semibold text-[#0f1724]"></h3>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<FilterDropdown
|
<FilterDropdown
|
||||||
label="Status"
|
label="Status"
|
||||||
|
|||||||
@ -10,8 +10,6 @@ import {
|
|||||||
History,
|
History,
|
||||||
CreditCard,
|
CreditCard,
|
||||||
Edit,
|
Edit,
|
||||||
CheckCircle2,
|
|
||||||
XCircle,
|
|
||||||
Settings,
|
Settings,
|
||||||
Image as ImageIcon,
|
Image as ImageIcon,
|
||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
@ -25,8 +23,10 @@ import {
|
|||||||
import { UsersTable, RolesTable } from '@/components/superadmin';
|
import { UsersTable, RolesTable } from '@/components/superadmin';
|
||||||
import { tenantService } from '@/services/tenant-service';
|
import { tenantService } from '@/services/tenant-service';
|
||||||
import { auditLogService } from '@/services/audit-log-service';
|
import { auditLogService } from '@/services/audit-log-service';
|
||||||
import type { Tenant, AssignedModule } from '@/types/tenant';
|
import { moduleService } from '@/services/module-service';
|
||||||
|
import type { Tenant } from '@/types/tenant';
|
||||||
import type { AuditLog } from '@/types/audit-log';
|
import type { AuditLog } from '@/types/audit-log';
|
||||||
|
import type { MyModule } from '@/types/module';
|
||||||
import { formatDate } from '@/utils/format-date';
|
import { formatDate } from '@/utils/format-date';
|
||||||
|
|
||||||
type TabType = 'overview' | 'users' | 'roles' | 'modules' | 'settings' | 'license' | 'audit-logs' | 'billing';
|
type TabType = 'overview' | 'users' | 'roles' | 'modules' | 'settings' | 'license' | 'audit-logs' | 'billing';
|
||||||
@ -278,10 +278,8 @@ const TenantDetails = (): ReactElement => {
|
|||||||
{activeTab === 'roles' && id && (
|
{activeTab === 'roles' && id && (
|
||||||
<RolesTable tenantId={id} compact={true} />
|
<RolesTable tenantId={id} compact={true} />
|
||||||
)}
|
)}
|
||||||
{activeTab === 'modules' && tenant && (
|
{activeTab === 'modules' && id && (
|
||||||
<ModulesTab
|
<ModulesTab tenantId={id} />
|
||||||
modules={tenant.assignedModules || []}
|
|
||||||
/>
|
|
||||||
)}
|
)}
|
||||||
{activeTab === 'settings' && tenant && (
|
{activeTab === 'settings' && tenant && (
|
||||||
<SettingsTab tenant={tenant} />
|
<SettingsTab tenant={tenant} />
|
||||||
@ -386,31 +384,51 @@ const OverviewTab = ({ tenant, stats }: OverviewTabProps): ReactElement => {
|
|||||||
|
|
||||||
// Modules Tab Component
|
// Modules Tab Component
|
||||||
interface ModulesTabProps {
|
interface ModulesTabProps {
|
||||||
modules: AssignedModule[];
|
tenantId: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const ModulesTab = ({ modules }: ModulesTabProps): ReactElement => {
|
const ModulesTab = ({ tenantId }: ModulesTabProps): ReactElement => {
|
||||||
const [enabledModules, setEnabledModules] = useState<Set<string>>(new Set());
|
const [modules, setModules] = useState<MyModule[]>([]);
|
||||||
|
const [isLoading, setIsLoading] = useState<boolean>(false);
|
||||||
|
const [error, setError] = useState<string | null>(null);
|
||||||
|
|
||||||
useEffect(() => {
|
// Fetch modules for this tenant
|
||||||
// Initialize enabled modules (assuming all are enabled by default)
|
const fetchModules = async (): Promise<void> => {
|
||||||
setEnabledModules(new Set(modules.map((m) => m.id)));
|
try {
|
||||||
}, [modules]);
|
setIsLoading(true);
|
||||||
|
setError(null);
|
||||||
const toggleModule = (moduleId: string): void => {
|
const response = await moduleService.getMyModules(tenantId);
|
||||||
setEnabledModules((prev) => {
|
if (response.success && response.data) {
|
||||||
const next = new Set(prev);
|
setModules(response.data);
|
||||||
if (next.has(moduleId)) {
|
|
||||||
next.delete(moduleId);
|
|
||||||
} else {
|
} else {
|
||||||
next.add(moduleId);
|
setError('Failed to load modules');
|
||||||
|
}
|
||||||
|
} catch (err: any) {
|
||||||
|
setError(err?.response?.data?.error?.message || 'Failed to load modules');
|
||||||
|
} finally {
|
||||||
|
setIsLoading(false);
|
||||||
}
|
}
|
||||||
return next;
|
|
||||||
});
|
|
||||||
// TODO: Call API to enable/disable module
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const columns: Column<AssignedModule>[] = [
|
useEffect(() => {
|
||||||
|
if (tenantId) {
|
||||||
|
fetchModules();
|
||||||
|
}
|
||||||
|
}, [tenantId]);
|
||||||
|
|
||||||
|
// Launch module handler
|
||||||
|
const handleLaunchModule = async (moduleId: string): Promise<void> => {
|
||||||
|
try {
|
||||||
|
const response = await moduleService.launch(moduleId, tenantId);
|
||||||
|
if (response.success && response.data.launch_url) {
|
||||||
|
window.open(response.data.launch_url, '_blank', 'noopener,noreferrer');
|
||||||
|
}
|
||||||
|
} catch (err: any) {
|
||||||
|
setError(err?.response?.data?.error?.message || 'Failed to launch module');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const columns: Column<MyModule>[] = [
|
||||||
{
|
{
|
||||||
key: 'name',
|
key: 'name',
|
||||||
label: 'Module Name',
|
label: 'Module Name',
|
||||||
@ -423,6 +441,13 @@ const ModulesTab = ({ modules }: ModulesTabProps): ReactElement => {
|
|||||||
</div>
|
</div>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
key: 'module_id',
|
||||||
|
label: 'Module ID',
|
||||||
|
render: (module) => (
|
||||||
|
<span className="text-sm font-normal text-[#6b7280] font-mono">{module.module_id}</span>
|
||||||
|
),
|
||||||
|
},
|
||||||
{
|
{
|
||||||
key: 'version',
|
key: 'version',
|
||||||
label: 'Version',
|
label: 'Version',
|
||||||
@ -448,45 +473,47 @@ const ModulesTab = ({ modules }: ModulesTabProps): ReactElement => {
|
|||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'enabled',
|
key: 'base_url',
|
||||||
label: 'Enabled',
|
label: 'Base URL',
|
||||||
render: (module) => (
|
render: (module) => (
|
||||||
<button
|
<span className="text-sm font-normal text-[#6b7280] font-mono truncate max-w-[200px]">
|
||||||
onClick={() => toggleModule(module.id)}
|
{module.base_url || 'N/A'}
|
||||||
className={`flex items-center gap-2 px-3 py-1.5 rounded-md text-xs font-medium transition-colors ${
|
</span>
|
||||||
enabledModules.has(module.id)
|
|
||||||
? 'bg-green-50 text-green-700 border border-green-200'
|
|
||||||
: 'bg-gray-50 text-gray-700 border border-gray-200'
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
{enabledModules.has(module.id) ? (
|
|
||||||
<CheckCircle2 className="w-4 h-4" />
|
|
||||||
) : (
|
|
||||||
<XCircle className="w-4 h-4" />
|
|
||||||
)}
|
|
||||||
<span>{enabledModules.has(module.id) ? 'Enabled' : 'Disabled'}</span>
|
|
||||||
</button>
|
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'created_at',
|
key: 'assigned_at',
|
||||||
label: 'Registered',
|
label: 'Assigned At',
|
||||||
render: (module) => (
|
render: (module) => (
|
||||||
<span className="text-sm font-normal text-[#6b7280]">{formatDate(module.created_at)}</span>
|
<span className="text-sm font-normal text-[#6b7280]">{formatDate(module.assigned_at)}</span>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'actions',
|
||||||
|
label: 'Actions',
|
||||||
|
align: 'right',
|
||||||
|
render: (module) => (
|
||||||
|
<div className="flex justify-end gap-3">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => handleLaunchModule(module.id)}
|
||||||
|
className="text-sm text-[#FFC107] hover:text-[#FFC107] font-medium transition-colors cursor-pointer"
|
||||||
|
>
|
||||||
|
Lunch
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col gap-4">
|
<div className="flex flex-col gap-4">
|
||||||
<div className="flex items-center justify-between">
|
|
||||||
<h3 className="text-lg font-semibold text-[#0f1724]">Modules</h3>
|
|
||||||
</div>
|
|
||||||
<DataTable
|
<DataTable
|
||||||
columns={columns}
|
columns={columns}
|
||||||
data={modules}
|
data={modules}
|
||||||
keyExtractor={(module) => module.id}
|
keyExtractor={(module) => module.id}
|
||||||
isLoading={false}
|
isLoading={isLoading}
|
||||||
|
error={error}
|
||||||
emptyMessage="No modules assigned to this tenant"
|
emptyMessage="No modules assigned to this tenant"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@ -593,9 +620,9 @@ const AuditLogsTab = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col gap-4">
|
<div className="flex flex-col gap-4">
|
||||||
<div className="flex items-center justify-between">
|
{/* <div className="flex items-center justify-between">
|
||||||
<h3 className="text-lg font-semibold text-[#0f1724]">Audit Logs</h3>
|
<h3 className="text-lg font-semibold text-[#0f1724]">Audit Logs</h3>
|
||||||
</div>
|
</div> */}
|
||||||
<DataTable
|
<DataTable
|
||||||
columns={columns}
|
columns={columns}
|
||||||
data={auditLogs}
|
data={auditLogs}
|
||||||
|
|||||||
@ -75,8 +75,13 @@ export const moduleService = {
|
|||||||
const response = await apiClient.post<LaunchModuleResponse>(url);
|
const response = await apiClient.post<LaunchModuleResponse>(url);
|
||||||
return response.data;
|
return response.data;
|
||||||
},
|
},
|
||||||
getMyModules: async (): Promise<MyModulesResponse> => {
|
getMyModules: async (tenantId?: string | null): Promise<MyModulesResponse> => {
|
||||||
const response = await apiClient.get<MyModulesResponse>('/modules/my');
|
const params = new URLSearchParams();
|
||||||
|
if (tenantId) {
|
||||||
|
params.append('tenant_id', tenantId);
|
||||||
|
}
|
||||||
|
const url = `/modules/my${params.toString() ? `?${params.toString()}` : ''}`;
|
||||||
|
const response = await apiClient.get<MyModulesResponse>(url);
|
||||||
return response.data;
|
return response.data;
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user