+ {/*
*/}
-
+ {/*
{module.module_id}
-
-
-
*/}
+ {/* */}
+
diff --git a/src/components/superadmin/NewModuleModal.tsx b/src/components/superadmin/NewModuleModal.tsx
index f62386b..cd22993 100644
--- a/src/components/superadmin/NewModuleModal.tsx
+++ b/src/components/superadmin/NewModuleModal.tsx
@@ -1,71 +1,122 @@
-import { useEffect, useState } from 'react';
-import type { ReactElement } from 'react';
-import { useForm } from 'react-hook-form';
-import { zodResolver } from '@hookform/resolvers/zod';
-import { z } from 'zod';
-import { Modal, FormField, PrimaryButton, SecondaryButton } from '@/components/shared';
-import { Copy, Check } from 'lucide-react';
-import { showToast } from '@/utils/toast';
+import { useEffect, useState } from "react";
+import type { ReactElement } from "react";
+import { useForm } from "react-hook-form";
+import { zodResolver } from "@hookform/resolvers/zod";
+import { z } from "zod";
+import {
+ Modal,
+ FormField,
+ PrimaryButton,
+ SecondaryButton,
+ FormTextArea,
+} from "@/components/shared";
+import { Copy, Check } from "lucide-react";
+import { showToast } from "@/utils/toast";
// Validation schema - matches backend validation
const newModuleSchema = z.object({
module_id: z
.string()
- .min(1, 'module_id is required')
- .min(3, 'module_id must be at least 3 characters')
- .max(100, 'module_id must be at most 100 characters'),
+ .min(1, "module_id is required")
+ .min(3, "module_id must be at least 3 characters")
+ .max(100, "module_id must be at most 100 characters"),
name: z
.string()
- .min(1, 'name is required')
- .min(3, 'name must be at least 3 characters')
- .max(100, 'name must be at most 100 characters'),
- description: z.string().max(1000, 'description must be at most 1000 characters').optional().nullable(),
+ .min(1, "name is required")
+ .min(3, "name must be at least 3 characters")
+ .max(100, "name must be at most 100 characters"),
+ description: z
+ .string()
+ .max(1000, "description must be at most 1000 characters")
+ .optional()
+ .nullable(),
version: z
.string()
- .min(1, 'version is required')
- .max(20, 'version must be at most 20 characters')
- .regex(/^[0-9]+\.[0-9]+\.[0-9]+$/, 'version format is invalid (must be X.Y.Z)'),
+ .min(1, "version is required")
+ .max(20, "version must be at most 20 characters")
+ .regex(
+ /^[0-9]+\.[0-9]+\.[0-9]+$/,
+ "version format is invalid (must be X.Y.Z)",
+ ),
runtime_language: z
.string()
- .min(1, 'runtime_language is required')
- .max(50, 'runtime_language must be at most 50 characters'),
- framework: z.string().max(50, 'framework must be at most 50 characters').optional().nullable(),
+ .min(1, "runtime_language is required")
+ .max(50, "runtime_language must be at most 50 characters"),
+ framework: z
+ .string()
+ .max(50, "framework must be at most 50 characters")
+ .optional()
+ .nullable(),
webhookurl: z
.union([
- z.string().url("Invalid URL format").max(500, "webhookurl must be at most 500 characters"),
+ z
+ .string()
+ .url("Invalid URL format")
+ .max(500, "webhookurl must be at most 500 characters"),
z.literal("").transform(() => null),
z.null(),
])
.optional(),
sync_webhook_url: z
.union([
- z.string().url("Invalid URL format").max(500, "sync_webhook_url must be at most 500 characters"),
+ z
+ .string()
+ .url("Invalid URL format")
+ .max(500, "sync_webhook_url must be at most 500 characters"),
z.literal("").transform(() => null),
z.null(),
])
.optional(),
frontend_base_url: z
.string()
- .min(1, 'frontend_base_url is required')
- .max(255, 'frontend_base_url must be at most 255 characters')
- .url('Invalid URL format'),
+ .min(1, "frontend_base_url is required")
+ .max(255, "frontend_base_url must be at most 255 characters")
+ .url("Invalid URL format"),
backend_base_url: z
.string()
- .min(1, 'backend_base_url is required')
- .max(255, 'backend_base_url must be at most 255 characters')
- .url('Invalid URL format'),
+ .min(1, "backend_base_url is required")
+ .max(255, "backend_base_url must be at most 255 characters")
+ .url("Invalid URL format"),
health_endpoint: z
.string()
- .min(1, 'health_endpoint is required')
- .max(255, 'health_endpoint must be at most 255 characters'),
+ .min(1, "health_endpoint is required")
+ .max(255, "health_endpoint must be at most 255 characters"),
endpoints: z.any().optional().nullable(),
kafka_topics: z.any().optional().nullable(),
- cpu_request: z.string().max(20, 'cpu_request must be at most 20 characters').optional().nullable(),
- cpu_limit: z.string().max(20, 'cpu_limit must be at most 20 characters').optional().nullable(),
- memory_request: z.string().max(20, 'memory_request must be at most 20 characters').optional().nullable(),
- memory_limit: z.string().max(20, 'memory_limit must be at most 20 characters').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(),
+ cpu_request: z
+ .string()
+ .max(20, "cpu_request must be at most 20 characters")
+ .optional()
+ .nullable(),
+ cpu_limit: z
+ .string()
+ .max(20, "cpu_limit must be at most 20 characters")
+ .optional()
+ .nullable(),
+ memory_request: z
+ .string()
+ .max(20, "memory_request must be at most 20 characters")
+ .optional()
+ .nullable(),
+ memory_limit: z
+ .string()
+ .max(20, "memory_limit must be at most 20 characters")
+ .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(),
last_health_check: z.string().optional().nullable(),
consecutive_failures: z.number().int().optional().nullable(),
registered_by: z.uuid().optional().nullable(),
@@ -125,16 +176,16 @@ export const NewModuleModal = ({
useEffect(() => {
if (!isOpen) {
reset({
- module_id: '',
- name: '',
+ module_id: "",
+ name: "",
description: null,
- version: '',
- runtime_language: '',
+ version: "",
+ runtime_language: "",
framework: null,
webhookurl: null,
- frontend_base_url: '',
- backend_base_url: '',
- health_endpoint: '',
+ frontend_base_url: "",
+ backend_base_url: "",
+ health_endpoint: "",
endpoints: null,
kafka_topics: null,
cpu_request: null,
@@ -164,38 +215,75 @@ export const NewModuleModal = ({
if (response?.api_key?.key) {
setApiKey(response.api_key.key);
showToast.success(
- 'Module registered successfully',
- 'Your API key has been generated. Please copy and store it securely - this is the only time you will see it.'
+ "Module registered successfully",
+ "Your API key has been generated. Please copy and store it securely - this is the only time you will see it.",
);
} else {
- showToast.success('Module registered successfully');
+ showToast.success("Module registered successfully");
onClose();
}
} catch (error: any) {
// Handle validation errors from API
- if (error?.response?.data?.details && Array.isArray(error.response.data.details)) {
+ if (
+ error?.response?.data?.details &&
+ Array.isArray(error.response.data.details)
+ ) {
const validationErrors = error.response.data.details;
- validationErrors.forEach((detail: { path: string; message: string }) => {
- if (detail.path === 'name' || detail.path === 'module_id' || detail.path === 'description' || detail.path === 'version' || detail.path === 'runtime_language' || detail.path === 'framework' || detail.path === 'webhookurl' || detail.path === 'sync_webhook_url' || detail.path === 'frontend_base_url' || detail.path === 'backend_base_url' || detail.path === 'health_endpoint' || detail.path === 'endpoints' || detail.path === 'kafka_topics' || detail.path === 'cpu_request' || detail.path === 'cpu_limit' || detail.path === 'memory_request' || detail.path === 'memory_limit' || detail.path === 'min_replicas' || detail.path === 'max_replicas' || detail.path === 'last_health_check' || detail.path === 'consecutive_failures' || detail.path === 'registered_by' || detail.path === 'tenant_id' || detail.path === 'metadata') {
- setError(detail.path as keyof NewModuleFormData, {
- type: 'server',
- message: detail.message,
- });
- }
- });
+ validationErrors.forEach(
+ (detail: { path: string; message: string }) => {
+ if (
+ detail.path === "name" ||
+ detail.path === "module_id" ||
+ detail.path === "description" ||
+ detail.path === "version" ||
+ detail.path === "runtime_language" ||
+ detail.path === "framework" ||
+ detail.path === "webhookurl" ||
+ detail.path === "sync_webhook_url" ||
+ detail.path === "frontend_base_url" ||
+ detail.path === "backend_base_url" ||
+ detail.path === "health_endpoint" ||
+ detail.path === "endpoints" ||
+ detail.path === "kafka_topics" ||
+ detail.path === "cpu_request" ||
+ detail.path === "cpu_limit" ||
+ detail.path === "memory_request" ||
+ detail.path === "memory_limit" ||
+ detail.path === "min_replicas" ||
+ detail.path === "max_replicas" ||
+ detail.path === "last_health_check" ||
+ detail.path === "consecutive_failures" ||
+ detail.path === "registered_by" ||
+ detail.path === "tenant_id" ||
+ detail.path === "metadata"
+ ) {
+ setError(detail.path as keyof NewModuleFormData, {
+ type: "server",
+ message: detail.message,
+ });
+ }
+ },
+ );
} else {
// Handle general errors
// Check for nested error object with message property
const errorObj = error?.response?.data?.error;
const errorMessage =
- (typeof errorObj === 'object' && errorObj !== null && 'message' in errorObj ? errorObj.message : null) ||
- (typeof errorObj === 'string' ? errorObj : null) ||
+ (typeof errorObj === "object" &&
+ errorObj !== null &&
+ "message" in errorObj
+ ? errorObj.message
+ : null) ||
+ (typeof errorObj === "string" ? errorObj : null) ||
error?.response?.data?.message ||
error?.message ||
- 'Failed to create module. Please try again.';
- setError('root', {
- type: 'server',
- message: typeof errorMessage === 'string' ? errorMessage : 'Failed to create module. Please try again.',
+ "Failed to create module. Please try again.";
+ setError("root", {
+ type: "server",
+ message:
+ typeof errorMessage === "string"
+ ? errorMessage
+ : "Failed to create module. Please try again.",
});
}
}
@@ -207,9 +295,9 @@ export const NewModuleModal = ({
await navigator.clipboard.writeText(apiKey);
setCopied(true);
setTimeout(() => setCopied(false), 2000);
- showToast.success('API key copied to clipboard');
+ showToast.success("API key copied to clipboard");
} catch (err) {
- showToast.error('Failed to copy API key');
+ showToast.error("Failed to copy API key");
}
}
};
@@ -235,7 +323,7 @@ export const NewModuleModal = ({
disabled={isLoading}
className="px-4 py-2.5 text-sm"
>
- {apiKey ? 'Close' : 'Cancel'}
+ {apiKey ? "Close" : "Cancel"}
{!apiKey && (
- {isLoading ? 'Registering...' : 'Register Module'}
+ {isLoading ? "Registering..." : "Register Module"}
)}
>
@@ -260,11 +348,16 @@ export const NewModuleModal = ({
⚠️ Important: Save Your API Key
- Your API key has been generated. This is the only time you will see this key. Store it securely in your module project to authenticate with QAssure services. If you lose this key, you cannot retrieve it.
+ Your API key has been generated. This is the{" "}
+ only time you will see this key. Store it
+ securely in your module project to authenticate with QAssure
+ services. If you lose this key, you cannot retrieve it.