Merge pull request 'ameenah' (#18) from ameenah into dev

Reviewed-on: https://gitea.maskantech.in/gitea_admin/saas_frontend/pulls/18
This commit is contained in:
furqan
2026-04-03 09:38:38 +00:00
5 changed files with 165 additions and 138 deletions
@@ -1,7 +1,7 @@
import { useState, useEffect } from 'react'; import { useState, useEffect } from 'react';
import { X } from 'lucide-react';
import { adminModuleApi } from '../AdminModuleApi'; import { adminModuleApi } from '../AdminModuleApi';
import type { ModuleEnvironment, EnvironmentCreate, EnvironmentUpdate } from '../AdminModuleTypes'; import type { ModuleEnvironment, EnvironmentCreate, EnvironmentUpdate } from '../AdminModuleTypes';
import CustomModal from '../../../../components/custom/CustomModal';
interface EnvironmentFormProps { interface EnvironmentFormProps {
moduleId: string; moduleId: string;
@@ -47,7 +47,7 @@ const EnvironmentForm = ({ moduleId, environment, onClose }: EnvironmentFormProp
setLoading(true); setLoading(true);
setError(''); setError('');
const trust_credentials = formData.trust_type === 'hmac' const trust_credentials = formData.trust_type === 'hmac'
? { hmac_secret: formData.hmac_secret } ? { hmac_secret: formData.hmac_secret }
: { secret_key: formData.hmac_secret }; : { secret_key: formData.hmac_secret };
@@ -90,157 +90,173 @@ const EnvironmentForm = ({ moduleId, environment, onClose }: EnvironmentFormProp
}; };
return ( return (
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 p-4"> <CustomModal
<div className="bg-white rounded-lg shadow-xl max-w-3xl w-full max-h-[90vh] overflow-y-auto"> isOpen={true}
<div className="sticky top-0 bg-white border-b px-6 py-4 flex items-center justify-between"> onClose={() => onClose(false)}
<h2 className="text-xl font-semibold">{environment ? 'Edit' : 'Create'} Environment</h2> title={environment ? "Edit Environment" : "Create Environment"}
<button onClick={() => onClose(false)} className="p-2 hover:bg-gray-100 rounded-lg"> size="lg"
<X size={20} /> footer={
<div className="flex gap-3 w-full">
<button
type="submit"
form="env-form"
disabled={loading}
className="flex-1 px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 disabled:opacity-50 transition-colors"
>
{loading ? "Saving..." : environment ? "Update" : "Create"}
</button>
<button
type="button"
onClick={() => onClose(false)}
className="flex-1 px-4 py-2 bg-[#334155] text-white rounded-lg hover:bg-[#1e293b] transition-colors"
>
Cancel
</button> </button>
</div> </div>
}
<form onSubmit={handleSubmit} className="p-6 space-y-4"> >
{error && <div className="bg-red-50 border border-red-200 text-red-700 px-4 py-3 rounded-lg">{error}</div>} <form id="env-form" onSubmit={handleSubmit} className="space-y-4">
{error && (
<div className="grid grid-cols-2 gap-4"> <div className="bg-red-50 border border-red-200 text-red-700 px-4 py-3 rounded-lg">
<div> {error}
<label className="block text-sm font-medium mb-1">Environment Slug *</label>
<input
type="text"
value={formData.slug}
onChange={(e) => setFormData({ ...formData, slug: e.target.value })}
className="w-full px-3 py-2 border rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500"
placeholder="prod, staging, eu-prod"
required
disabled={!!environment}
/>
</div>
<div>
<label className="block text-sm font-medium mb-1">Trust Type *</label>
<select
value={formData.trust_type}
onChange={(e) => setFormData({ ...formData, trust_type: e.target.value })}
className="w-full px-3 py-2 border rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500"
>
<option value="hmac">HMAC-SHA256</option>
<option value="static_key">Static Key</option>
</select>
</div>
</div> </div>
)}
<div className="grid grid-cols-2 gap-4">
<div> <div>
<label className="block text-sm font-medium mb-1">Frontend Base URL *</label> <label className="block text-sm font-medium mb-1 text-(--text-primary)">
<input Environment Slug *
type="url" </label>
value={formData.frontend_base_url}
onChange={(e) => setFormData({ ...formData, frontend_base_url: e.target.value })}
className="w-full px-3 py-2 border rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500"
placeholder="https://module.example.com"
required
/>
</div>
<div>
<label className="block text-sm font-medium mb-1">Backend Base URL *</label>
<input
type="url"
value={formData.backend_base_url}
onChange={(e) => setFormData({ ...formData, backend_base_url: e.target.value })}
className="w-full px-3 py-2 border rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500"
placeholder="https://api.module.example.com"
required
/>
</div>
<div className="grid grid-cols-2 gap-4">
<div>
<label className="block text-sm font-medium mb-1">SSO Entry Path *</label>
<input
type="text"
value={formData.sso_entry_path}
onChange={(e) => setFormData({ ...formData, sso_entry_path: e.target.value })}
className="w-full px-3 py-2 border rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500"
required
/>
</div>
<div>
<label className="block text-sm font-medium mb-1">Permission Sync Endpoint *</label>
<input
type="text"
value={formData.permission_sync_endpoint}
onChange={(e) => setFormData({ ...formData, permission_sync_endpoint: e.target.value })}
className="w-full px-3 py-2 border rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500"
required
/>
</div>
</div>
<div>
<label className="block text-sm font-medium mb-1">Provisioning Endpoint *</label>
<input <input
type="text" type="text"
value={formData.provisioning_endpoint} value={formData.slug}
onChange={(e) => setFormData({ ...formData, provisioning_endpoint: e.target.value })} onChange={(e) => setFormData({ ...formData, slug: e.target.value })}
className="w-full px-3 py-2 border rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500" className="w-full px-3 py-2 bg-(--background) border border-(--card-border) text-(--text-primary) rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500"
placeholder="prod, staging, eu-prod"
required
disabled={!!environment}
/>
</div>
<div>
<label className="block text-sm font-medium mb-1 text-(--text-primary)">
Trust Type
</label>
<input
type="text"
value="HMAC-SHA256"
disabled
className="w-full px-3 py-2 bg-(--background) border border-(--card-border) text-(--text-secondary) rounded-lg opacity-60 cursor-not-allowed"
/>
</div>
</div>
<div>
<label className="block text-sm font-medium mb-1 text-(--text-primary)">
Frontend Base URL *
</label>
<input
type="url"
value={formData.frontend_base_url}
onChange={(e) => setFormData({ ...formData, frontend_base_url: e.target.value })}
className="w-full px-3 py-2 bg-(--background) border border-(--card-border) text-(--text-primary) rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500"
placeholder="https://module.example.com"
required
/>
</div>
<div>
<label className="block text-sm font-medium mb-1 text-(--text-primary)">
Backend Base URL *
</label>
<input
type="url"
value={formData.backend_base_url}
onChange={(e) => setFormData({ ...formData, backend_base_url: e.target.value })}
className="w-full px-3 py-2 bg-(--background) border border-(--card-border) text-(--text-primary) rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500"
placeholder="https://api.module.example.com"
required
/>
</div>
<div className="grid grid-cols-2 gap-4">
<div>
<label className="block text-sm font-medium mb-1 text-(--text-primary)">
SSO Entry Path *
</label>
<input
type="text"
value={formData.sso_entry_path}
onChange={(e) => setFormData({ ...formData, sso_entry_path: e.target.value })}
className="w-full px-3 py-2 bg-(--background) border border-(--card-border) text-(--text-primary) rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500"
required required
/> />
</div> </div>
<div> <div>
<label className="block text-sm font-medium mb-1"> <label className="block text-sm font-medium mb-1 text-(--text-primary)">
{formData.trust_type === 'hmac' ? 'HMAC Secret' : 'Secret Key'} {!environment && '*'} Permission Sync Endpoint *
</label> </label>
<input <input
type="password" type="text"
value={formData.hmac_secret} value={formData.permission_sync_endpoint}
onChange={(e) => setFormData({ ...formData, hmac_secret: e.target.value })} onChange={(e) => setFormData({ ...formData, permission_sync_endpoint: e.target.value })}
className="w-full px-3 py-2 border rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500" className="w-full px-3 py-2 bg-(--background) border border-(--card-border) text-(--text-primary) rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500"
placeholder={environment ? "Leave blank to keep existing" : "Enter secret"} required
required={!environment}
/> />
<p className="text-xs text-gray-500 mt-1">Stored securely on backend</p>
</div> </div>
</div>
<div className="flex items-center gap-4"> <div>
<label className="flex items-center gap-2"> <label className="block text-sm font-medium mb-1 text-(--text-primary)">
<input Provisioning Endpoint *
type="checkbox" </label>
checked={formData.is_default} <input
onChange={(e) => setFormData({ ...formData, is_default: e.target.checked })} type="text"
className="rounded" value={formData.provisioning_endpoint}
/> onChange={(e) => setFormData({ ...formData, provisioning_endpoint: e.target.value })}
<span className="text-sm">Set as default environment</span> className="w-full px-3 py-2 bg-(--background) border border-(--card-border) text-(--text-primary) rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500"
</label> required
<label className="flex items-center gap-2"> />
<input </div>
type="checkbox"
checked={formData.is_active}
onChange={(e) => setFormData({ ...formData, is_active: e.target.checked })}
className="rounded"
/>
<span className="text-sm">Active</span>
</label>
</div>
<div className="flex gap-3 pt-4"> <div>
<button <label className="block text-sm font-medium mb-1 text-(--text-primary)">
type="submit" HMAC Secret {!environment && "*"}
disabled={loading} </label>
className="flex-1 px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 disabled:opacity-50" <input
> type="password"
{loading ? 'Saving...' : (environment ? 'Update' : 'Create')} value={formData.hmac_secret}
</button> onChange={(e) => setFormData({ ...formData, hmac_secret: e.target.value })}
<button className="w-full px-3 py-2 bg-(--background) border border-(--card-border) text-(--text-primary) rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500"
type="button" placeholder={environment ? "Leave blank to keep existing" : "Enter secret"}
onClick={() => onClose(false)} required={!environment}
className="flex-1 px-4 py-2 bg-gray-200 rounded-lg hover:bg-gray-300" />
> <p className="text-xs text-(--text-secondary) mt-1">
Cancel Stored securely on backend
</button> </p>
</div> </div>
</form>
</div> <div className="flex items-center gap-4">
</div> <label className="flex items-center gap-2 cursor-pointer">
<input
type="checkbox"
checked={formData.is_default}
onChange={(e) => setFormData({ ...formData, is_default: e.target.checked })}
className="rounded border-(--card-border) bg-(--background)"
/>
<span className="text-sm text-(--text-primary)">Set as default</span>
</label>
<label className="flex items-center gap-2 cursor-pointer">
<input
type="checkbox"
checked={formData.is_active}
onChange={(e) => setFormData({ ...formData, is_active: e.target.checked })}
className="rounded border-(--card-border) bg-(--background)"
/>
<span className="text-sm text-(--text-primary)">Active</span>
</label>
</div>
</form>
</CustomModal>
); );
}; };
+7
View File
@@ -75,6 +75,12 @@ const navItems: NavItem[] = [
path: "/admin/modules", path: "/admin/modules",
access: "modules.view", access: "modules.view",
}, },
{
icon: <LayoutGrid size={22} />,
name: "Logs",
path: "/logs",
access: "admin.logs.read",
},
{ {
icon: <Settings size={22} />, icon: <Settings size={22} />,
name: "Settings", name: "Settings",
@@ -111,6 +117,7 @@ const AppSidebar: React.FC = () => {
'Dropdowns': 'dropdowns', 'Dropdowns': 'dropdowns',
'Settings': 'settings', 'Settings': 'settings',
'Modules': 'modules', 'Modules': 'modules',
'Logs': 'logs',
}; };
const key = keyMap[name]; const key = keyMap[name];
+1
View File
@@ -73,6 +73,7 @@
"status": { "status": {
"active": "نشط", "active": "نشط",
"inactive": "غير نشط", "inactive": "غير نشط",
"default": "افتراضي",
"pending": "قيد الانتظار", "pending": "قيد الانتظار",
"approved": "موافق عليه", "approved": "موافق عليه",
"rejected": "مرفوض", "rejected": "مرفوض",
+1
View File
@@ -73,6 +73,7 @@
"status": { "status": {
"active": "Active", "active": "Active",
"inactive": "Inactive", "inactive": "Inactive",
"default": "Default",
"pending": "Pending", "pending": "Pending",
"approved": "Approved", "approved": "Approved",
"rejected": "Rejected", "rejected": "Rejected",
+4 -2
View File
@@ -4,6 +4,7 @@ import SignInPage from "../application/authentication/SignInPage";
import SignUpPage from "../application/authentication/SignUpPage"; import SignUpPage from "../application/authentication/SignUpPage";
import ResetPasswordPage from "../application/authentication/ResetPasswordPage"; import ResetPasswordPage from "../application/authentication/ResetPasswordPage";
import Roles from "../application/roles"; import Roles from "../application/roles";
import LogsPage from "../application/logs/LogsPage";
import Dashboard from "../application/dashboard/Dashboard"; import Dashboard from "../application/dashboard/Dashboard";
import ProfilePage from "../application/profile/ProfilePage"; import ProfilePage from "../application/profile/ProfilePage";
import Tenants from "../application/tenants"; import Tenants from "../application/tenants";
@@ -31,11 +32,12 @@ const AppRoutes = () => {
<Route path="/theme/*" element={<Theme />} /> <Route path="/theme/*" element={<Theme />} />
<Route path="/settings" element={<SettingsPage />} /> <Route path="/settings" element={<SettingsPage />} />
<Route path="/users/*" element={<Users />} /> <Route path="/users/*" element={<Users />} />
<Route path="/logs" element={<LogsPage />} />
{/* Admin Module Routes */} {/* Admin Module Routes */}
<Route path="/admin/modules/*" element={<Modules />} /> <Route path="/admin/modules/*" element={<Modules />} />
<Route path="/admin/tenants/:tenantId/modules" element={<TenantModuleAssignment />} /> <Route path="/admin/tenants/:tenantId/modules" element={<TenantModuleAssignment />} />
<Route path="/" element={<Navigate to="/dashboard" replace />} /> <Route path="/" element={<Navigate to="/dashboard" replace />} />
</Route> </Route>