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:
@@ -1,7 +1,7 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
import { X } from 'lucide-react';
|
||||
import { adminModuleApi } from '../AdminModuleApi';
|
||||
import type { ModuleEnvironment, EnvironmentCreate, EnvironmentUpdate } from '../AdminModuleTypes';
|
||||
import CustomModal from '../../../../components/custom/CustomModal';
|
||||
|
||||
interface EnvironmentFormProps {
|
||||
moduleId: string;
|
||||
@@ -47,7 +47,7 @@ const EnvironmentForm = ({ moduleId, environment, onClose }: EnvironmentFormProp
|
||||
setLoading(true);
|
||||
setError('');
|
||||
|
||||
const trust_credentials = formData.trust_type === 'hmac'
|
||||
const trust_credentials = formData.trust_type === 'hmac'
|
||||
? { hmac_secret: formData.hmac_secret }
|
||||
: { secret_key: formData.hmac_secret };
|
||||
|
||||
@@ -90,157 +90,173 @@ const EnvironmentForm = ({ moduleId, environment, onClose }: EnvironmentFormProp
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 p-4">
|
||||
<div className="bg-white rounded-lg shadow-xl max-w-3xl w-full max-h-[90vh] overflow-y-auto">
|
||||
<div className="sticky top-0 bg-white border-b px-6 py-4 flex items-center justify-between">
|
||||
<h2 className="text-xl font-semibold">{environment ? 'Edit' : 'Create'} Environment</h2>
|
||||
<button onClick={() => onClose(false)} className="p-2 hover:bg-gray-100 rounded-lg">
|
||||
<X size={20} />
|
||||
<CustomModal
|
||||
isOpen={true}
|
||||
onClose={() => onClose(false)}
|
||||
title={environment ? "Edit Environment" : "Create Environment"}
|
||||
size="lg"
|
||||
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>
|
||||
</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>}
|
||||
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div>
|
||||
<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>
|
||||
}
|
||||
>
|
||||
<form id="env-form" onSubmit={handleSubmit} className="space-y-4">
|
||||
{error && (
|
||||
<div className="bg-red-50 border border-red-200 text-red-700 px-4 py-3 rounded-lg">
|
||||
{error}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label className="block text-sm font-medium mb-1">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 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>
|
||||
<label className="block text-sm font-medium mb-1 text-(--text-primary)">
|
||||
Environment Slug *
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
value={formData.provisioning_endpoint}
|
||||
onChange={(e) => setFormData({ ...formData, provisioning_endpoint: e.target.value })}
|
||||
className="w-full px-3 py-2 border rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||
value={formData.slug}
|
||||
onChange={(e) => setFormData({ ...formData, slug: 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="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
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium mb-1">
|
||||
{formData.trust_type === 'hmac' ? 'HMAC Secret' : 'Secret Key'} {!environment && '*'}
|
||||
<label className="block text-sm font-medium mb-1 text-(--text-primary)">
|
||||
Permission Sync Endpoint *
|
||||
</label>
|
||||
<input
|
||||
type="password"
|
||||
value={formData.hmac_secret}
|
||||
onChange={(e) => setFormData({ ...formData, hmac_secret: e.target.value })}
|
||||
className="w-full px-3 py-2 border rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||
placeholder={environment ? "Leave blank to keep existing" : "Enter secret"}
|
||||
required={!environment}
|
||||
type="text"
|
||||
value={formData.permission_sync_endpoint}
|
||||
onChange={(e) => setFormData({ ...formData, permission_sync_endpoint: 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
|
||||
/>
|
||||
<p className="text-xs text-gray-500 mt-1">Stored securely on backend</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-4">
|
||||
<label className="flex items-center gap-2">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={formData.is_default}
|
||||
onChange={(e) => setFormData({ ...formData, is_default: e.target.checked })}
|
||||
className="rounded"
|
||||
/>
|
||||
<span className="text-sm">Set as default environment</span>
|
||||
</label>
|
||||
<label className="flex items-center gap-2">
|
||||
<input
|
||||
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>
|
||||
<label className="block text-sm font-medium mb-1 text-(--text-primary)">
|
||||
Provisioning Endpoint *
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
value={formData.provisioning_endpoint}
|
||||
onChange={(e) => setFormData({ ...formData, provisioning_endpoint: 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
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex gap-3 pt-4">
|
||||
<button
|
||||
type="submit"
|
||||
disabled={loading}
|
||||
className="flex-1 px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 disabled:opacity-50"
|
||||
>
|
||||
{loading ? 'Saving...' : (environment ? 'Update' : 'Create')}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => onClose(false)}
|
||||
className="flex-1 px-4 py-2 bg-gray-200 rounded-lg hover:bg-gray-300"
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium mb-1 text-(--text-primary)">
|
||||
HMAC Secret {!environment && "*"}
|
||||
</label>
|
||||
<input
|
||||
type="password"
|
||||
value={formData.hmac_secret}
|
||||
onChange={(e) => setFormData({ ...formData, hmac_secret: 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={environment ? "Leave blank to keep existing" : "Enter secret"}
|
||||
required={!environment}
|
||||
/>
|
||||
<p className="text-xs text-(--text-secondary) mt-1">
|
||||
Stored securely on backend
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-4">
|
||||
<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>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -75,6 +75,12 @@ const navItems: NavItem[] = [
|
||||
path: "/admin/modules",
|
||||
access: "modules.view",
|
||||
},
|
||||
{
|
||||
icon: <LayoutGrid size={22} />,
|
||||
name: "Logs",
|
||||
path: "/logs",
|
||||
access: "admin.logs.read",
|
||||
},
|
||||
{
|
||||
icon: <Settings size={22} />,
|
||||
name: "Settings",
|
||||
@@ -111,6 +117,7 @@ const AppSidebar: React.FC = () => {
|
||||
'Dropdowns': 'dropdowns',
|
||||
'Settings': 'settings',
|
||||
'Modules': 'modules',
|
||||
'Logs': 'logs',
|
||||
};
|
||||
|
||||
const key = keyMap[name];
|
||||
|
||||
@@ -73,6 +73,7 @@
|
||||
"status": {
|
||||
"active": "نشط",
|
||||
"inactive": "غير نشط",
|
||||
"default": "افتراضي",
|
||||
"pending": "قيد الانتظار",
|
||||
"approved": "موافق عليه",
|
||||
"rejected": "مرفوض",
|
||||
|
||||
@@ -73,6 +73,7 @@
|
||||
"status": {
|
||||
"active": "Active",
|
||||
"inactive": "Inactive",
|
||||
"default": "Default",
|
||||
"pending": "Pending",
|
||||
"approved": "Approved",
|
||||
"rejected": "Rejected",
|
||||
|
||||
@@ -4,6 +4,7 @@ import SignInPage from "../application/authentication/SignInPage";
|
||||
import SignUpPage from "../application/authentication/SignUpPage";
|
||||
import ResetPasswordPage from "../application/authentication/ResetPasswordPage";
|
||||
import Roles from "../application/roles";
|
||||
import LogsPage from "../application/logs/LogsPage";
|
||||
import Dashboard from "../application/dashboard/Dashboard";
|
||||
import ProfilePage from "../application/profile/ProfilePage";
|
||||
import Tenants from "../application/tenants";
|
||||
@@ -31,11 +32,12 @@ const AppRoutes = () => {
|
||||
<Route path="/theme/*" element={<Theme />} />
|
||||
<Route path="/settings" element={<SettingsPage />} />
|
||||
<Route path="/users/*" element={<Users />} />
|
||||
|
||||
<Route path="/logs" element={<LogsPage />} />
|
||||
|
||||
{/* Admin Module Routes */}
|
||||
<Route path="/admin/modules/*" element={<Modules />} />
|
||||
<Route path="/admin/tenants/:tenantId/modules" element={<TenantModuleAssignment />} />
|
||||
|
||||
|
||||
<Route path="/" element={<Navigate to="/dashboard" replace />} />
|
||||
</Route>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user