import React, { useState } from 'react';
import {
X,
Smartphone,
ShieldAlert,
Clock,
Activity,
} from 'lucide-react';
import { useKccStore, useOperatorRole } from '../store/kccStore';
import { StatusBadge } from './StatusBadge';
import { MoneyDisplay } from './MoneyDisplay';
import { HeartbeatTimeline } from './HeartbeatTimeline';
import { PlatformStatus } from '../contracts';
export const InspectorDrawer: React.FC = () => {
const inspectorTarget = useKccStore((state) => state.inspectorTarget);
const closeInspector = useKccStore((state) => state.closeInspector);
const { permissions } = useOperatorRole();
const organizations = useKccStore((state) => state.organizations);
const servers = useKccStore((state) => state.servers);
const incidents = useKccStore((state) => state.incidents);
const modules = useKccStore((state) => state.modules);
const installations = useKccStore((state) => state.installations);
const subscriptions = useKccStore((state) => state.subscriptions);
const instances = useKccStore((state) => state.instances);
const auditLog = useKccStore((state) => state.auditLog);
const openConfirmModal = useKccStore((state) => state.openConfirmModal);
const updateOrganizationStatus = useKccStore((state) => state.updateOrganizationStatus);
const grantModuleTrial = useKccStore((state) => state.grantModuleTrial);
const resolveIncident = useKccStore((state) => state.resolveIncident);
const requestServerDiagnostics = useKccStore((state) => state.requestServerDiagnostics);
const scheduleServerMaintenance = useKccStore((state) => state.scheduleServerMaintenance);
const [orgActiveTab, setOrgActiveTab] = useState<
'overview' | 'users' | 'modules' | 'installations' | 'billing' | 'incidents' | 'audit'
>('overview');
const [selectedTrialModule, setSelectedTrialModule] = useState('krona-omnichannel');
const [trialDurationDays, setTrialDurationDays] = useState(14);
if (!inspectorTarget) return null;
const renderContent = () => {
switch (inspectorTarget.type) {
case 'organization': {
const org = organizations.find((o) => o.tenantId === inspectorTarget.id);
if (!org) return
{/* Header */}
ИНН: {org.inn}
{org.displayName}
{org.domain} • ID: {org.tenantId.slice(0, 8)}...
{/* Operator Actions Bar */}
Действия оператора платформы
{org.platformStatus !== 'active' && (
handleStatusChange('active')}
className="px-2.5 py-1.5 rounded-lg text-xs font-medium bg-emerald-950/70 text-emerald-300 border border-emerald-700/50 hover:bg-emerald-900/50 disabled:opacity-40 transition-colors"
>
Активировать (Active)
)}
{org.platformStatus !== 'grace' && (
handleStatusChange('grace')}
className="px-2.5 py-1.5 rounded-lg text-xs font-medium bg-orange-950/70 text-orange-300 border border-orange-700/50 hover:bg-orange-900/50 disabled:opacity-40 transition-colors"
>
Назначить Grace
)}
{org.platformStatus !== 'suspended' && (
handleStatusChange('suspended')}
className="px-2.5 py-1.5 rounded-lg text-xs font-medium bg-rose-950/70 text-rose-300 border border-rose-700/50 hover:bg-rose-900/50 disabled:opacity-40 transition-colors"
>
Заблокировать (Suspend)
)}
{/* Trial grant inline form */}
{permissions.canGrantTrialGrace && (
Выдать Trial на модуль:
setSelectedTrialModule(e.target.value)}
className="bg-zinc-900 border border-zinc-700 rounded-md px-2 py-1 text-zinc-200 text-xs focus:outline-hidden"
>
{modules.map((m) => (
{m.name}
))}
setTrialDurationDays(Number(e.target.value))}
className="bg-zinc-900 border border-zinc-700 rounded-md px-2 py-1 text-zinc-200 text-xs focus:outline-hidden"
>
7 дней
14 дней
30 дней
Выдать Trial
)}
{/* Navigation Tabs */}
{[
{ id: 'overview', label: 'Обзор' },
{ id: 'users', label: `Пользователи (${org.userCount})` },
{ id: 'modules', label: `Модули (${orgInstances.length})` },
{ id: 'installations', label: `Установки (${orgInstalls.length})` },
{ id: 'billing', label: 'Платежи' },
{ id: 'incidents', label: `Инциденты (${orgIncidents.length})` },
{ id: 'audit', label: 'Аудит' },
].map((tab) => (
setOrgActiveTab(tab.id as typeof orgActiveTab)}
className={`px-3 py-1.5 rounded-t-md font-medium whitespace-nowrap transition-colors ${
orgActiveTab === tab.id
? 'bg-zinc-800/80 text-amber-300 border-b-2 border-amber-400'
: 'text-zinc-400 hover:text-zinc-200 hover:bg-zinc-900'
}`}
>
{tab.label}
))}
{/* Tab Contents */}
{orgActiveTab === 'overview' && (
Тарифный план
{org.tariffPlan}
Филиалов / точек
{org.branchCount}
Задолженность
0 ? 'text-rose-400' : 'text-emerald-400'}`}
/>
Контакты организации:
Email: {org.contactEmail}
Телефон: {org.contactPhone}
Последняя активность: {new Date(org.lastActiveAt).toLocaleString('ru-RU')}
Окончание подписки: {new Date(org.subscriptionEndAt).toLocaleDateString('ru-RU')}
)}
{orgActiveTab === 'users' && (
Пользователи-агрегаты
{org.userCount} учетных записей
В соответствии с контрактом безопасности Control Center отображает только деперсонализированные
агрегаты тенанта. Персональные данные сотрудников защищены контуром организации.
Менеджеры / операторы
{Math.max(0, org.userCount - 6)}
)}
{orgActiveTab === 'modules' && (
{orgInstances.map((inst) => (
{inst.moduleName}
v{inst.currentVersion} • {inst.serverName}
))}
)}
{orgActiveTab === 'installations' && (
{orgInstalls.map((ins) => (
{ins.deviceName}
{ins.kind.toUpperCase()} • v{ins.appVersion} • {ins.osVersion}
))}
{orgInstalls.length === 0 &&
Нет активных установок
}
)}
{orgActiveTab === 'billing' && (
{sub ? (
Тарифная подписка
Период:
{sub.period === 'year' ? 'Годовой' : 'Ежемесячный'}
Сумма тарифа:
Действует до:
{new Date(sub.validUntil).toLocaleDateString('ru-RU')}
) : (
Данные подписки отсутствуют
)}
)}
{orgActiveTab === 'incidents' && (
{orgIncidents.map((inc) => (
{inc.incidentNumber}
{inc.summary}
))}
{orgIncidents.length === 0 &&
Инцидентов нет
}
)}
{orgActiveTab === 'audit' && (
{orgAudits.map((a) => (
{a.action}
{new Date(a.occurredAt).toLocaleTimeString('ru-RU')}
{a.reason}
{a.operatorName} ({a.operatorRole})
))}
{orgAudits.length === 0 &&
Записей аудита нет
}
)}
);
}
case 'server': {
const srv = servers.find((s) => s.serverId === inspectorTarget.id);
if (!srv) return
{srv.hostingProvider} • {srv.region}
{srv.name}
{srv.ipAddress} • {srv.hostname}
{/* Operator Actions */}
Операции с инфраструктурным узлом
Запросить диагностику
Запланировать ТО
{/* Telemetry Metrics */}
Телеметрия узла (Server Monitor Agent):
Agent v{srv.agentVersion} ({srv.agentStatus})
CPU Нагрузка:
{srv.cpuLoadPercent}%
80 ? 'bg-rose-500' : 'bg-emerald-500'}`}
style={{ width: `${srv.cpuLoadPercent}%` }}
/>
RAM Память:
{Math.round(srv.ramUsedBytes / (1024 * 1024 * 1024))} / {Math.round(srv.ramTotalBytes / (1024 * 1024 * 1024))} GB
Диск:
{Math.round(srv.diskUsedBytes / (1024 * 1024 * 1024))} / {Math.round(srv.diskTotalBytes / (1024 * 1024 * 1024))} GB
0.85 ? 'bg-rose-500' : 'bg-amber-400'}`}
style={{ width: `${(srv.diskUsedBytes / srv.diskTotalBytes) * 100}%` }}
/>
Сеть (Rx / Tx):
{(srv.networkRxBytesPerSec / 1024 / 1024).toFixed(1)} / {(srv.networkTxBytesPerSec / 1024 / 1024).toFixed(1)} MB/s
{/* Heartbeat timeline */}
Пульс узла (Heartbeat 24h):
RTT: {srv.latencyMs}ms
{/* Backup & Restore Drill */}
Бэкап:
{srv.backup.lastSuccessfulAt ? new Date(srv.backup.lastSuccessfulAt).toLocaleString('ru-RU') : 'Нет успешных копий'}
{srv.backup.targetProvider}
Restore Drill:
{srv.restoreDrill.verifiedAt ? new Date(srv.restoreDrill.verifiedAt).toLocaleDateString('ru-RU') : 'Никогда / Устарел'}
{srv.restoreDrill.drillDurationSeconds ? `Время восстановления: ${Math.round(srv.restoreDrill.drillDurationSeconds / 60)} мин.` : 'Тест не проводился'}
{/* Module instances on server */}
Модули на узле ({srvInstances.length}):
{srvInstances.map((inst) => (
{inst.moduleName}
{inst.tenantName}
))}
);
}
case 'incident': {
const inc = incidents.find((i) => i.incidentId === inspectorTarget.id);
if (!inc) return
Инцидент не найден
;
const handleResolve = () => {
openConfirmModal({
title: `Разрешение инцидента ${inc.incidentNumber}`,
actionName: 'incident.resolve',
impactSummary: `Инцидент будет переведен в статус resolved, дежурная смена будет оповещена об устранении причин.`,
targetType: 'incident',
targetId: inc.incidentId,
targetName: inc.incidentNumber,
onConfirm: async (reason) => {
await resolveIncident(inc.incidentId, reason);
},
});
};
return (
{inc.severity} Severity
{inc.incidentNumber}
{inc.summary}
{inc.status !== 'resolved' && (
Действие оператора:
Отметить инцидент как устраненный с обоснованием
Разрешить инцидент
)}
Описание инцидента:
{inc.description}
Ответственный: {inc.ownerName}
Хронология расследования (Timeline):
{inc.timeline.map((step, idx) => (
{step.author}
{new Date(step.timestamp).toLocaleTimeString('ru-RU')}
{step.message}
))}
);
}
default:
return
Детальная информация для данного типа пока формируется
;
}
};
return (
{/* Drawer Header */}
{/* Drawer Body */}
{renderContent()}
);
};