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,52 +274,52 @@ 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">
|
||||||
|
{/* Basic Information Section */}
|
||||||
|
<div className="mb-4">
|
||||||
|
<h3 className="text-sm font-semibold text-[#0f1724] mb-3">Basic Information</h3>
|
||||||
|
<div className="flex flex-col gap-0">
|
||||||
|
<div className="flex gap-5">
|
||||||
|
<div className="flex-1">
|
||||||
|
<FormField
|
||||||
|
label="Module ID"
|
||||||
|
required
|
||||||
|
placeholder="Enter module ID (e.g., my-module)"
|
||||||
|
error={errors.module_id?.message}
|
||||||
|
{...register('module_id')}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="flex-1">
|
||||||
|
|
||||||
<div className="flex flex-col gap-0">
|
<FormField
|
||||||
{/* Basic Information Section */}
|
label="Module Name"
|
||||||
<div className="mb-4">
|
required
|
||||||
<h3 className="text-sm font-semibold text-[#0f1724] mb-3">Basic Information</h3>
|
placeholder="Enter module name"
|
||||||
<div className="flex flex-col gap-0">
|
error={errors.name?.message}
|
||||||
<div className="flex gap-5">
|
{...register('name')}
|
||||||
<div className="flex-1">
|
/>
|
||||||
<FormField
|
</div>
|
||||||
label="Module ID"
|
|
||||||
required
|
|
||||||
placeholder="Enter module ID (e.g., my-module)"
|
|
||||||
error={errors.module_id?.message}
|
|
||||||
{...register('module_id')}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
<div className="flex-1">
|
|
||||||
|
|
||||||
<FormField
|
<FormField
|
||||||
label="Module Name"
|
label="Description"
|
||||||
required
|
placeholder="Enter module description (optional)"
|
||||||
placeholder="Enter module name"
|
error={errors.description?.message}
|
||||||
error={errors.name?.message}
|
{...register('description')}
|
||||||
{...register('name')}
|
/>
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<FormField
|
{/* <div className="flex gap-5">
|
||||||
label="Description"
|
|
||||||
placeholder="Enter module description (optional)"
|
|
||||||
error={errors.description?.message}
|
|
||||||
{...register('description')}
|
|
||||||
/>
|
|
||||||
|
|
||||||
{/* <div className="flex gap-5">
|
|
||||||
<div className="flex-1"> */}
|
<div className="flex-1"> */}
|
||||||
<FormField
|
<FormField
|
||||||
label="Version"
|
label="Version"
|
||||||
required
|
required
|
||||||
placeholder="e.g., 1.0.0"
|
placeholder="e.g., 1.0.0"
|
||||||
error={errors.version?.message}
|
error={errors.version?.message}
|
||||||
{...register('version')}
|
{...register('version')}
|
||||||
/>
|
/>
|
||||||
{/* </div> */}
|
{/* </div> */}
|
||||||
{/* <div className="flex-1">
|
{/* <div className="flex-1">
|
||||||
<FormSelect
|
<FormSelect
|
||||||
label="Status"
|
label="Status"
|
||||||
placeholder="Select Status"
|
placeholder="Select Status"
|
||||||
@ -330,151 +330,152 @@ export const NewModuleModal = ({
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div> */}
|
</div> */}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Runtime Information Section */}
|
{/* Runtime Information Section */}
|
||||||
<div className="mb-4">
|
<div className="mb-4">
|
||||||
<h3 className="text-sm font-semibold text-[#0f1724] mb-3">Runtime Information</h3>
|
<h3 className="text-sm font-semibold text-[#0f1724] mb-3">Runtime Information</h3>
|
||||||
<div className="flex gap-5">
|
<div className="flex gap-5">
|
||||||
<div className="flex-1">
|
<div className="flex-1">
|
||||||
|
<FormField
|
||||||
|
label="Runtime Language"
|
||||||
|
required
|
||||||
|
placeholder="e.g., Node.js, Python, Java"
|
||||||
|
error={errors.runtime_language?.message}
|
||||||
|
{...register('runtime_language')}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="flex-1">
|
||||||
|
<FormField
|
||||||
|
label="Framework"
|
||||||
|
placeholder="e.g., Express, Django, Spring (optional)"
|
||||||
|
error={errors.framework?.message}
|
||||||
|
{...register('framework')}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="flex gap-5">
|
||||||
|
<div className="flex-1">
|
||||||
|
<FormField
|
||||||
|
label="Webhook URL"
|
||||||
|
placeholder="e.g., https://example.com/webhook"
|
||||||
|
error={errors.webhookurl?.message}
|
||||||
|
{...register('webhookurl')}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="flex-1">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* URL Configuration Section */}
|
||||||
|
<div className="mb-4">
|
||||||
|
<h3 className="text-sm font-semibold text-[#0f1724] mb-3">URL Configuration</h3>
|
||||||
|
<div className="flex flex-col gap-0">
|
||||||
<FormField
|
<FormField
|
||||||
label="Runtime Language"
|
label="Base URL"
|
||||||
required
|
required
|
||||||
placeholder="e.g., Node.js, Python, Java"
|
type="url"
|
||||||
error={errors.runtime_language?.message}
|
placeholder="https://example.com"
|
||||||
{...register('runtime_language')}
|
error={errors.base_url?.message}
|
||||||
|
{...register('base_url')}
|
||||||
/>
|
/>
|
||||||
</div>
|
|
||||||
<div className="flex-1">
|
|
||||||
<FormField
|
<FormField
|
||||||
label="Framework"
|
label="Health Endpoint"
|
||||||
placeholder="e.g., Express, Django, Spring (optional)"
|
required
|
||||||
error={errors.framework?.message}
|
placeholder="/health"
|
||||||
{...register('framework')}
|
error={errors.health_endpoint?.message}
|
||||||
|
{...register('health_endpoint')}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex gap-5">
|
|
||||||
<div className="flex-1">
|
|
||||||
<FormField
|
|
||||||
label="Webhook URL"
|
|
||||||
placeholder="e.g., https://example.com/webhook"
|
|
||||||
error={errors.webhookurl?.message}
|
|
||||||
{...register('webhookurl')}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="flex-1">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* URL Configuration Section */}
|
{/* Resource Configuration Section */}
|
||||||
<div className="mb-4">
|
<div className="mb-4">
|
||||||
<h3 className="text-sm font-semibold text-[#0f1724] mb-3">URL Configuration</h3>
|
<h3 className="text-sm font-semibold text-[#0f1724] mb-3">Resource Configuration (Optional)</h3>
|
||||||
<div className="flex flex-col gap-0">
|
<div className="flex flex-col gap-0">
|
||||||
<FormField
|
<div className="flex gap-5">
|
||||||
label="Base URL"
|
<div className="flex-1">
|
||||||
required
|
<FormField
|
||||||
type="url"
|
label="CPU Request"
|
||||||
placeholder="https://example.com"
|
placeholder="e.g., 100m, 0.5"
|
||||||
error={errors.base_url?.message}
|
error={errors.cpu_request?.message}
|
||||||
{...register('base_url')}
|
{...register('cpu_request')}
|
||||||
/>
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="flex-1">
|
||||||
|
<FormField
|
||||||
|
label="CPU Limit"
|
||||||
|
placeholder="e.g., 500m, 1"
|
||||||
|
error={errors.cpu_limit?.message}
|
||||||
|
{...register('cpu_limit')}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<FormField
|
<div className="flex gap-5">
|
||||||
label="Health Endpoint"
|
<div className="flex-1">
|
||||||
required
|
<FormField
|
||||||
placeholder="/health"
|
label="Memory Request"
|
||||||
error={errors.health_endpoint?.message}
|
placeholder="e.g., 128Mi, 512Mi"
|
||||||
{...register('health_endpoint')}
|
error={errors.memory_request?.message}
|
||||||
/>
|
{...register('memory_request')}
|
||||||
</div>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
<div className="flex-1">
|
||||||
|
<FormField
|
||||||
|
label="Memory Limit"
|
||||||
|
placeholder="e.g., 256Mi, 1Gi"
|
||||||
|
error={errors.memory_limit?.message}
|
||||||
|
{...register('memory_limit')}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
{/* Resource Configuration Section */}
|
<div className="flex gap-5">
|
||||||
<div className="mb-4">
|
<div className="flex-1">
|
||||||
<h3 className="text-sm font-semibold text-[#0f1724] mb-3">Resource Configuration (Optional)</h3>
|
<FormField
|
||||||
<div className="flex flex-col gap-0">
|
label="Min Replicas"
|
||||||
<div className="flex gap-5">
|
type="number"
|
||||||
<div className="flex-1">
|
min="1"
|
||||||
<FormField
|
max="50"
|
||||||
label="CPU Request"
|
step="1"
|
||||||
placeholder="e.g., 100m, 0.5"
|
placeholder="1"
|
||||||
error={errors.cpu_request?.message}
|
error={errors.min_replicas?.message}
|
||||||
{...register('cpu_request')}
|
{...register('min_replicas', {
|
||||||
/>
|
setValueAs: (value) => {
|
||||||
</div>
|
if (value === '' || value === null || value === undefined) return null;
|
||||||
<div className="flex-1">
|
const num = Number(value);
|
||||||
<FormField
|
return isNaN(num) ? null : num;
|
||||||
label="CPU Limit"
|
},
|
||||||
placeholder="e.g., 500m, 1"
|
})}
|
||||||
error={errors.cpu_limit?.message}
|
/>
|
||||||
{...register('cpu_limit')}
|
</div>
|
||||||
/>
|
<div className="flex-1">
|
||||||
</div>
|
<FormField
|
||||||
</div>
|
label="Max Replicas"
|
||||||
|
type="number"
|
||||||
<div className="flex gap-5">
|
min="1"
|
||||||
<div className="flex-1">
|
max="50"
|
||||||
<FormField
|
step="1"
|
||||||
label="Memory Request"
|
placeholder="5"
|
||||||
placeholder="e.g., 128Mi, 512Mi"
|
error={errors.max_replicas?.message}
|
||||||
error={errors.memory_request?.message}
|
{...register('max_replicas', {
|
||||||
{...register('memory_request')}
|
setValueAs: (value) => {
|
||||||
/>
|
if (value === '' || value === null || value === undefined) return null;
|
||||||
</div>
|
const num = Number(value);
|
||||||
<div className="flex-1">
|
return isNaN(num) ? null : num;
|
||||||
<FormField
|
},
|
||||||
label="Memory Limit"
|
})}
|
||||||
placeholder="e.g., 256Mi, 1Gi"
|
/>
|
||||||
error={errors.memory_limit?.message}
|
</div>
|
||||||
{...register('memory_limit')}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="flex gap-5">
|
|
||||||
<div className="flex-1">
|
|
||||||
<FormField
|
|
||||||
label="Min Replicas"
|
|
||||||
type="number"
|
|
||||||
min="1"
|
|
||||||
max="50"
|
|
||||||
step="1"
|
|
||||||
placeholder="1"
|
|
||||||
error={errors.min_replicas?.message}
|
|
||||||
{...register('min_replicas', {
|
|
||||||
setValueAs: (value) => {
|
|
||||||
if (value === '' || value === null || value === undefined) return null;
|
|
||||||
const num = Number(value);
|
|
||||||
return isNaN(num) ? null : num;
|
|
||||||
},
|
|
||||||
})}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="flex-1">
|
|
||||||
<FormField
|
|
||||||
label="Max Replicas"
|
|
||||||
type="number"
|
|
||||||
min="1"
|
|
||||||
max="50"
|
|
||||||
step="1"
|
|
||||||
placeholder="5"
|
|
||||||
error={errors.max_replicas?.message}
|
|
||||||
{...register('max_replicas', {
|
|
||||||
setValueAs: (value) => {
|
|
||||||
if (value === '' || value === null || value === undefined) return null;
|
|
||||||
const num = Number(value);
|
|
||||||
return isNaN(num) ? null : num;
|
|
||||||
},
|
|
||||||
})}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</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');
|
||||||
}
|
}
|
||||||
return next;
|
} catch (err: any) {
|
||||||
});
|
setError(err?.response?.data?.error?.message || 'Failed to load modules');
|
||||||
// TODO: Call API to enable/disable module
|
} finally {
|
||||||
|
setIsLoading(false);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
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