Overhauled lockout process
This commit is contained in:
@@ -962,6 +962,7 @@ export const TenantManagementPage: React.FC = () => {
|
||||
isOpen={isCreateModalOpen}
|
||||
onClose={() => setIsCreateModalOpen(false)}
|
||||
title="Provision New Tenant Partition"
|
||||
subtitle="Define an isolated multi-tenant partition for grants, portfolios, and users."
|
||||
size="md"
|
||||
>
|
||||
<form
|
||||
@@ -970,17 +971,6 @@ export const TenantManagementPage: React.FC = () => {
|
||||
)}
|
||||
className="space-y-4"
|
||||
>
|
||||
<div className="p-3.5 rounded-xl bg-blue-950/20 border border-blue-800/30 text-xs text-blue-200 flex items-start gap-2.5">
|
||||
<Info className="w-4 h-4 text-blue-400 shrink-0 mt-0.5" />
|
||||
<div className="space-y-1 leading-relaxed">
|
||||
<p className="font-semibold text-white">Multi-Tenant Partition Boundary</p>
|
||||
<p>
|
||||
Creating a new tenant defines an isolated partition boundary for grants, portfolios, projects, and users.
|
||||
New tenants are automatically initialized in the Active state.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-xs font-semibold text-slate-300 mb-1.5">
|
||||
Tenant Organization Name <span className="text-rose-400">*</span>
|
||||
@@ -1007,7 +997,7 @@ export const TenantManagementPage: React.FC = () => {
|
||||
<button
|
||||
type="submit"
|
||||
disabled={createMutation.isPending}
|
||||
className="flex items-center gap-1.5 px-4 py-2 bg-gradient-to-r from-blue-600 to-indigo-600 hover:from-blue-500 hover:to-indigo-500 text-white text-xs font-semibold rounded-xl shadow-md transition disabled:opacity-50 cursor-pointer"
|
||||
className="flex items-center gap-1.5 px-4 py-2 bg-blue-600 hover:bg-blue-500 text-white text-xs font-semibold rounded-xl shadow-md transition disabled:opacity-50 cursor-pointer"
|
||||
>
|
||||
{createMutation.isPending && <RefreshCw className="w-3.5 h-3.5 animate-spin" />}
|
||||
<span>{createMutation.isPending ? 'Provisioning...' : 'Create Tenant'}</span>
|
||||
@@ -1021,6 +1011,7 @@ export const TenantManagementPage: React.FC = () => {
|
||||
isOpen={isUpdateModalOpen}
|
||||
onClose={() => setIsUpdateModalOpen(false)}
|
||||
title={`Update Tenant: ${selectedTenant?.name || ''}`}
|
||||
subtitle="Modify tenant partition name and operational access status."
|
||||
size="md"
|
||||
>
|
||||
{selectedTenant && (
|
||||
@@ -1064,13 +1055,12 @@ export const TenantManagementPage: React.FC = () => {
|
||||
|
||||
{/* Warning when selecting Inactive or Disabled */}
|
||||
{Number(updateForm.watch('status')) !== ActivityStatus.Active && (
|
||||
<div className="p-3 rounded-xl bg-amber-950/30 border border-amber-800/40 text-xs text-amber-200 flex items-start gap-2.5">
|
||||
<div className="p-3 rounded-xl bg-amber-500/10 border border-amber-500/20 text-xs text-amber-200 flex items-start gap-2.5">
|
||||
<AlertTriangle className="w-4 h-4 text-amber-400 shrink-0 mt-0.5" />
|
||||
<div className="space-y-1">
|
||||
<p className="font-semibold text-white">Multitenancy Lockout Warning</p>
|
||||
<p className="text-[11px] leading-relaxed text-amber-300/90">
|
||||
Changing this tenant's status to Inactive or Disabled immediately locks out all users assigned to this partition.
|
||||
The backend API will deny requests until the tenant is restored to Active status.
|
||||
<div className="space-y-0.5">
|
||||
<p className="font-semibold text-amber-300">Lockout Notice</p>
|
||||
<p className="text-[11px] text-amber-200/90 leading-relaxed">
|
||||
Setting this tenant to Inactive or Disabled immediately locks out assigned users from accessing the API.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1087,7 +1077,7 @@ export const TenantManagementPage: React.FC = () => {
|
||||
<button
|
||||
type="submit"
|
||||
disabled={updateMutation.isPending}
|
||||
className="flex items-center gap-1.5 px-4 py-2 bg-gradient-to-r from-blue-600 to-indigo-600 hover:from-blue-500 hover:to-indigo-500 text-white text-xs font-semibold rounded-xl shadow-md transition disabled:opacity-50 cursor-pointer"
|
||||
className="flex items-center gap-1.5 px-4 py-2 bg-blue-600 hover:bg-blue-500 text-white text-xs font-semibold rounded-xl shadow-md transition disabled:opacity-50 cursor-pointer"
|
||||
>
|
||||
{updateMutation.isPending && <RefreshCw className="w-3.5 h-3.5 animate-spin" />}
|
||||
<span>{updateMutation.isPending ? 'Saving...' : 'Update Tenant'}</span>
|
||||
@@ -1102,66 +1092,47 @@ export const TenantManagementPage: React.FC = () => {
|
||||
isOpen={isDetailModalOpen}
|
||||
onClose={() => setIsDetailModalOpen(false)}
|
||||
title={`Tenant Details: ${selectedTenant?.name || ''}`}
|
||||
subtitle="Partition boundary information, creation audit, and operational status."
|
||||
size="lg"
|
||||
>
|
||||
{selectedTenant && (
|
||||
<div className="space-y-5">
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4 bg-slate-950/60 p-4 rounded-xl border border-slate-800">
|
||||
<div className="space-y-4">
|
||||
<div className="grid grid-cols-2 sm:grid-cols-3 gap-3 bg-slate-950/60 p-3.5 rounded-xl border border-slate-800 text-xs">
|
||||
<div>
|
||||
<p className="text-[11px] text-slate-500 uppercase tracking-wider">Tenant Numeric ID</p>
|
||||
<p className="text-[10px] text-slate-500 uppercase tracking-wider font-semibold">Tenant ID</p>
|
||||
<p className="text-sm font-bold text-white mt-0.5">#{selectedTenant.id}</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<p className="text-[11px] text-slate-500 uppercase tracking-wider">Tenant Name</p>
|
||||
<p className="text-sm font-semibold text-slate-200 mt-0.5">{selectedTenant.name || '—'}</p>
|
||||
<p className="text-[10px] text-slate-500 uppercase tracking-wider font-semibold">Tenant Name</p>
|
||||
<p className="text-xs font-semibold text-slate-200 mt-0.5 truncate">{selectedTenant.name || '—'}</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<p className="text-[11px] text-slate-500 uppercase tracking-wider">Operational Status</p>
|
||||
<p className="text-[10px] text-slate-500 uppercase tracking-wider font-semibold">Operational Status</p>
|
||||
<div className="mt-1">
|
||||
<ActivityStatusBadge status={selectedTenant.status} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<p className="text-[11px] text-slate-500 uppercase tracking-wider">Created Date</p>
|
||||
<p className="text-[10px] text-slate-500 uppercase tracking-wider font-semibold">Created Date</p>
|
||||
<p className="text-xs text-slate-300 mt-0.5">{formatDate(selectedTenant.createdAt)}</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<p className="text-[11px] text-slate-500 uppercase tracking-wider">Created By</p>
|
||||
<p className="text-xs font-mono text-slate-300 mt-0.5">
|
||||
<p className="text-[10px] text-slate-500 uppercase tracking-wider font-semibold">Created By</p>
|
||||
<p className="text-xs font-mono text-slate-300 mt-0.5 truncate">
|
||||
{getUserEmail(selectedTenant.createdBy) || selectedTenant.createdBy || 'System Admin'}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{selectedTenant.updatedAt && (
|
||||
<div>
|
||||
<p className="text-[11px] text-slate-500 uppercase tracking-wider">Last Updated</p>
|
||||
<p className="text-[10px] text-slate-500 uppercase tracking-wider font-semibold">Last Updated</p>
|
||||
<p className="text-xs text-slate-300 mt-0.5">{formatDate(selectedTenant.updatedAt)}</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{selectedTenant.updatedBy && (
|
||||
<div>
|
||||
<p className="text-[11px] text-slate-500 uppercase tracking-wider">Updated By</p>
|
||||
<p className="text-xs font-mono text-slate-300 mt-0.5">
|
||||
{getUserEmail(selectedTenant.updatedBy) || selectedTenant.updatedBy}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="p-3.5 rounded-xl bg-slate-900/80 border border-slate-800 text-xs text-slate-300 space-y-2">
|
||||
<p className="font-semibold text-white flex items-center gap-1.5">
|
||||
<ShieldCheck className="w-4 h-4 text-blue-400" />
|
||||
Partition Scope & Authentication
|
||||
</p>
|
||||
<p className="text-[11px] text-slate-400 leading-relaxed">
|
||||
When users authenticate, their security token contains claims identifying their tenant partition.
|
||||
The system automatically scopes data queries to this tenant and enforces operational lockout if the status is not Active.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center justify-end gap-2.5 pt-3 border-t border-slate-800">
|
||||
@@ -1171,7 +1142,7 @@ export const TenantManagementPage: React.FC = () => {
|
||||
setIsDetailModalOpen(false);
|
||||
handleOpenUpdate(selectedTenant);
|
||||
}}
|
||||
className="flex items-center gap-1.5 px-4 py-2 bg-gradient-to-r from-blue-600 to-indigo-600 hover:from-blue-500 hover:to-indigo-500 text-white text-xs font-semibold rounded-xl transition cursor-pointer"
|
||||
className="flex items-center gap-1.5 px-4 py-2 bg-blue-600 hover:bg-blue-500 text-white text-xs font-semibold rounded-xl transition cursor-pointer"
|
||||
>
|
||||
<Edit2 className="w-3.5 h-3.5" />
|
||||
<span>Edit Tenant Status</span>
|
||||
|
||||
Reference in New Issue
Block a user