feat(governance): 顯示 Agent 可委派版本治理
This commit is contained in:
@@ -36,6 +36,8 @@ import { StatusOrb } from '@/components/ui/status-orb'
|
||||
import {
|
||||
apiClient,
|
||||
type AiAgentDeploymentLayoutSnapshot,
|
||||
type AiAgentHostStatefulVersionInventorySnapshot,
|
||||
type AiAgentProactiveOperationsContractSnapshot,
|
||||
type AiProviderRouteMatrixSnapshot,
|
||||
type AiAgentAutomationBacklogSnapshot,
|
||||
type AiAgentAutomationInventorySnapshot,
|
||||
@@ -313,6 +315,8 @@ export function AutomationInventoryTab() {
|
||||
const [observabilityMatrix, setObservabilityMatrix] = useState<ObservabilityContractMatrixSnapshot | null>(null)
|
||||
const [providerRouteMatrix, setProviderRouteMatrix] = useState<AiProviderRouteMatrixSnapshot | null>(null)
|
||||
const [deploymentLayout, setDeploymentLayout] = useState<AiAgentDeploymentLayoutSnapshot | null>(null)
|
||||
const [proactiveOperations, setProactiveOperations] = useState<AiAgentProactiveOperationsContractSnapshot | null>(null)
|
||||
const [hostStatefulInventory, setHostStatefulInventory] = useState<AiAgentHostStatefulVersionInventorySnapshot | null>(null)
|
||||
const [serviceHealthGapMatrix, setServiceHealthGapMatrix] = useState<ServiceHealthGapMatrixSnapshot | null>(null)
|
||||
const [serviceHealthNotificationPolicy, setServiceHealthNotificationPolicy] = useState<ServiceHealthFailureNotificationPolicySnapshot | null>(null)
|
||||
const [loading, setLoading] = useState(true)
|
||||
@@ -332,6 +336,8 @@ export function AutomationInventoryTab() {
|
||||
apiClient.getObservabilityContractMatrix(),
|
||||
apiClient.getAiProviderRouteMatrix(),
|
||||
apiClient.getAiAgentDeploymentLayout(),
|
||||
apiClient.getAiAgentProactiveOperationsContract(),
|
||||
apiClient.getAiAgentHostStatefulVersionInventory(),
|
||||
apiClient.getServiceHealthGapMatrix(),
|
||||
apiClient.getServiceHealthFailureNotificationPolicy(),
|
||||
] as const
|
||||
@@ -350,6 +356,8 @@ export function AutomationInventoryTab() {
|
||||
observabilityMatrixResult,
|
||||
providerRouteMatrixResult,
|
||||
deploymentLayoutResult,
|
||||
proactiveOperationsResult,
|
||||
hostStatefulInventoryResult,
|
||||
serviceHealthGapMatrixResult,
|
||||
serviceHealthNotificationPolicyResult,
|
||||
] = results
|
||||
@@ -365,6 +373,8 @@ export function AutomationInventoryTab() {
|
||||
setObservabilityMatrix(observabilityMatrixResult.status === 'fulfilled' ? observabilityMatrixResult.value : null)
|
||||
setProviderRouteMatrix(providerRouteMatrixResult.status === 'fulfilled' ? providerRouteMatrixResult.value : null)
|
||||
setDeploymentLayout(deploymentLayoutResult.status === 'fulfilled' ? deploymentLayoutResult.value : null)
|
||||
setProactiveOperations(proactiveOperationsResult.status === 'fulfilled' ? proactiveOperationsResult.value : null)
|
||||
setHostStatefulInventory(hostStatefulInventoryResult.status === 'fulfilled' ? hostStatefulInventoryResult.value : null)
|
||||
setServiceHealthGapMatrix(serviceHealthGapMatrixResult.status === 'fulfilled' ? serviceHealthGapMatrixResult.value : null)
|
||||
setServiceHealthNotificationPolicy(serviceHealthNotificationPolicyResult.status === 'fulfilled' ? serviceHealthNotificationPolicyResult.value : null)
|
||||
setError([
|
||||
@@ -378,6 +388,8 @@ export function AutomationInventoryTab() {
|
||||
observabilityMatrixResult,
|
||||
providerRouteMatrixResult,
|
||||
deploymentLayoutResult,
|
||||
proactiveOperationsResult,
|
||||
hostStatefulInventoryResult,
|
||||
serviceHealthGapMatrixResult,
|
||||
serviceHealthNotificationPolicyResult,
|
||||
].some(result => result.status === 'rejected'))
|
||||
@@ -444,6 +456,56 @@ export function AutomationInventoryTab() {
|
||||
.slice(0, 12)
|
||||
}, [deploymentLayout])
|
||||
|
||||
const visibleDelegableCapabilities = useMemo(() => {
|
||||
if (!proactiveOperations) return []
|
||||
const riskPriority = { critical: 0, high: 1, medium: 2, low: 3 } as Record<string, number>
|
||||
return [...proactiveOperations.delegable_capabilities]
|
||||
.sort((a, b) => {
|
||||
const riskLeft = riskPriority[a.risk_tier] ?? 4
|
||||
const riskRight = riskPriority[b.risk_tier] ?? 4
|
||||
if (riskLeft !== riskRight) return riskLeft - riskRight
|
||||
return a.capability_id.localeCompare(b.capability_id)
|
||||
})
|
||||
.slice(0, 8)
|
||||
}, [proactiveOperations])
|
||||
|
||||
const visibleVersionDomains = useMemo(() => {
|
||||
if (!proactiveOperations) return []
|
||||
const ownerPriority = { openclaw: 0, hermes: 1, nemotron: 2 } as Record<string, number>
|
||||
return [...proactiveOperations.version_lifecycle_domains]
|
||||
.sort((a, b) => {
|
||||
const ownerLeft = ownerPriority[a.primary_owner] ?? 3
|
||||
const ownerRight = ownerPriority[b.primary_owner] ?? 3
|
||||
if (ownerLeft !== ownerRight) return ownerLeft - ownerRight
|
||||
return a.domain_id.localeCompare(b.domain_id)
|
||||
})
|
||||
.slice(0, 8)
|
||||
}, [proactiveOperations])
|
||||
|
||||
const visibleHostInventory = useMemo(() => {
|
||||
if (!hostStatefulInventory) return []
|
||||
return [...hostStatefulInventory.host_inventory]
|
||||
.sort((a, b) => {
|
||||
if (a.maintenance_window_required !== b.maintenance_window_required) {
|
||||
return a.maintenance_window_required ? -1 : 1
|
||||
}
|
||||
return a.host_id.localeCompare(b.host_id)
|
||||
})
|
||||
.slice(0, 5)
|
||||
}, [hostStatefulInventory])
|
||||
|
||||
const visibleStatefulServices = useMemo(() => {
|
||||
if (!hostStatefulInventory) return []
|
||||
return [...hostStatefulInventory.stateful_services]
|
||||
.sort((a, b) => {
|
||||
if (a.backup_required_before_change !== b.backup_required_before_change) {
|
||||
return a.backup_required_before_change ? -1 : 1
|
||||
}
|
||||
return a.service_id.localeCompare(b.service_id)
|
||||
})
|
||||
.slice(0, 8)
|
||||
}, [hostStatefulInventory])
|
||||
|
||||
const visibleReadinessRows = useMemo(() => {
|
||||
if (!backupReadiness) return []
|
||||
const priority = { blocked: 0, action_required: 1, deferred: 2, ready: 3 } as Record<string, number>
|
||||
@@ -587,7 +649,7 @@ export function AutomationInventoryTab() {
|
||||
)
|
||||
}
|
||||
|
||||
if (error || !snapshot || !backlog || !backupTargets || !backupReadiness || !backupPolicy || !offsiteEscrow || !giteaHealth || !observabilityMatrix || !providerRouteMatrix || !deploymentLayout || !serviceHealthGapMatrix || !serviceHealthNotificationPolicy) {
|
||||
if (error || !snapshot || !backlog || !backupTargets || !backupReadiness || !backupPolicy || !offsiteEscrow || !giteaHealth || !observabilityMatrix || !providerRouteMatrix || !deploymentLayout || !proactiveOperations || !hostStatefulInventory || !serviceHealthGapMatrix || !serviceHealthNotificationPolicy) {
|
||||
return (
|
||||
<div style={{ padding: 20 }}>
|
||||
<GlassCard variant="subtle" padding="lg">
|
||||
@@ -660,6 +722,29 @@ export function AutomationInventoryTab() {
|
||||
const deploymentLayoutNemotronTargets = deploymentLayout.rollups.by_primary_agent.nemotron ?? 0
|
||||
const deploymentLayoutBlockedTargets = deploymentLayout.rollups.blocked_target_ids.length
|
||||
const deploymentLayoutApprovalTargets = deploymentLayout.rollups.approval_required_target_ids.length
|
||||
const proactiveOverall = proactiveOperations.program_status.overall_completion_percent
|
||||
const proactiveDelegableCapabilities = proactiveOperations.rollups.delegable_capability_count
|
||||
const proactiveApprovalCapabilities = proactiveOperations.rollups.approval_required_capability_count
|
||||
const proactiveBlockedVersionDomains = proactiveOperations.rollups.blocked_update_domain_ids.length
|
||||
const proactiveTelegramActionRequired = proactiveOperations.rollups.telegram_action_required_capability_ids.length
|
||||
const proactiveAutoExecuteAllowed = proactiveOperations.rollups.auto_execute_allowed_count
|
||||
const hostReadonlyDeniedCount = (
|
||||
hostStatefulInventory.rollups.ssh_login_allowed_count
|
||||
+ hostStatefulInventory.rollups.kubectl_command_execution_allowed_count
|
||||
+ hostStatefulInventory.rollups.apt_upgrade_allowed_count
|
||||
+ hostStatefulInventory.rollups.k3s_upgrade_allowed_count
|
||||
+ hostStatefulInventory.rollups.node_drain_allowed_count
|
||||
+ hostStatefulInventory.rollups.reboot_allowed_count
|
||||
+ hostStatefulInventory.rollups.stateful_service_restart_allowed_count
|
||||
+ hostStatefulInventory.rollups.telegram_direct_send_allowed_count
|
||||
+ hostStatefulInventory.rollups.conversation_transcript_allowed_count
|
||||
)
|
||||
const hostRedactionLocked = (
|
||||
hostStatefulInventory.display_redaction_contract.conversation_transcript_display_allowed === false
|
||||
&& hostStatefulInventory.display_redaction_contract.redaction_required === true
|
||||
)
|
||||
const hostTelegramDirectSendAllowed = hostStatefulInventory.telegram_policy.direct_send_allowed ? 1 : 0
|
||||
const hostTelegramGatewayWriteAllowed = hostStatefulInventory.telegram_policy.gateway_queue_write_allowed ? 1 : 0
|
||||
const serviceHealthActions = serviceHealthGapMatrix.rollups.target_ids_requiring_action.length
|
||||
const serviceHealthStaleGaps = serviceHealthGapMatrix.rollups.stale_endpoint_ids.length
|
||||
const serviceHealthOperatorReviews = serviceHealthGapMatrix.rollups.health_gap_ids.length
|
||||
@@ -820,6 +905,22 @@ export function AutomationInventoryTab() {
|
||||
}
|
||||
}
|
||||
|
||||
const proactiveValueLabel = (group: string, value: string) => {
|
||||
try {
|
||||
return t(`proactiveOperations.${group}.${value}` as never)
|
||||
} catch {
|
||||
return value
|
||||
}
|
||||
}
|
||||
|
||||
const hostStatefulValueLabel = (group: string, value: string) => {
|
||||
try {
|
||||
return t(`hostStateful.${group}.${value}` as never)
|
||||
} catch {
|
||||
return value
|
||||
}
|
||||
}
|
||||
|
||||
const serviceHealthValueLabel = (value: string) => {
|
||||
try {
|
||||
return t(`serviceHealth.values.${value}` as never)
|
||||
@@ -1109,6 +1210,171 @@ export function AutomationInventoryTab() {
|
||||
</div>
|
||||
</GlassCard>
|
||||
|
||||
<GlassCard variant="subtle" padding="md">
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 13, minWidth: 0 }}>
|
||||
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 12, flexWrap: 'wrap' }}>
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 7, minWidth: 0 }}>
|
||||
<HardDrive size={14} style={{ color: '#d97757' }} />
|
||||
<span style={{ fontFamily: 'Syne, sans-serif', fontSize: 13, fontWeight: 700, color: '#141413' }}>
|
||||
{t('proactiveOperations.title')}
|
||||
</span>
|
||||
</div>
|
||||
<Chip
|
||||
value={t('proactiveOperations.source', {
|
||||
generated: formatDateTime(proactiveOperations.generated_at),
|
||||
current: proactiveOperations.program_status.current_task_id,
|
||||
next: proactiveOperations.program_status.next_task_id,
|
||||
})}
|
||||
muted
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(6, minmax(0, 1fr))', gap: 12 }} className="automation-inventory-proactive-kpi-grid">
|
||||
<MetricCard label={t('proactiveOperations.metrics.overall')} value={`${proactiveOverall}%`} tone="ok" icon={<Gauge size={16} />} />
|
||||
<MetricCard label={t('proactiveOperations.metrics.capabilities')} value={proactiveDelegableCapabilities} icon={<Boxes size={16} />} />
|
||||
<MetricCard label={t('proactiveOperations.metrics.approval')} value={proactiveApprovalCapabilities} tone="warn" icon={<Lock size={16} />} />
|
||||
<MetricCard label={t('proactiveOperations.metrics.blockedDomains')} value={proactiveBlockedVersionDomains} tone="warn" icon={<AlertTriangle size={16} />} />
|
||||
<MetricCard label={t('proactiveOperations.metrics.telegram')} value={proactiveTelegramActionRequired} tone="warn" icon={<BellRing size={16} />} />
|
||||
<MetricCard label={t('proactiveOperations.metrics.autoExecute')} value={proactiveAutoExecuteAllowed} tone="ok" icon={<ShieldCheck size={16} />} />
|
||||
<MetricCard label={t('hostStateful.metrics.hosts')} value={hostStatefulInventory.rollups.host_count} icon={<Server size={16} />} />
|
||||
<MetricCard label={t('hostStateful.metrics.k3sNodes')} value={hostStatefulInventory.rollups.k3s_node_count} icon={<Route size={16} />} />
|
||||
<MetricCard label={t('hostStateful.metrics.stateful')} value={hostStatefulInventory.rollups.stateful_service_count} icon={<Database size={16} />} />
|
||||
<MetricCard label={t('hostStateful.metrics.probes')} value={hostStatefulInventory.rollups.readonly_probe_step_count} icon={<FileText size={16} />} />
|
||||
<MetricCard label={t('hostStateful.metrics.maintenance')} value={hostStatefulInventory.rollups.maintenance_required_field_count} tone="warn" icon={<ShieldAlert size={16} />} />
|
||||
<MetricCard label={t('hostStateful.metrics.denied')} value={hostReadonlyDeniedCount} tone="ok" icon={<BellOff size={16} />} />
|
||||
</div>
|
||||
|
||||
<div style={{ display: 'grid', gridTemplateColumns: 'minmax(0, 1.1fr) minmax(0, 0.9fr)', gap: 12 }} className="automation-inventory-proactive-grid">
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 12, minWidth: 0 }}>
|
||||
<div style={{ padding: 12, border: '0.5px solid #e0ddd4', borderRadius: 7, background: '#fff', display: 'flex', flexDirection: 'column', gap: 10, minWidth: 0 }}>
|
||||
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 10, flexWrap: 'wrap' }}>
|
||||
<span style={{ fontFamily: 'Syne, sans-serif', fontSize: 13, fontWeight: 700, color: '#141413' }}>
|
||||
{t('proactiveOperations.capabilitiesTitle')}
|
||||
</span>
|
||||
<Chip value={t('proactiveOperations.capabilitiesShown', { count: visibleDelegableCapabilities.length, total: proactiveDelegableCapabilities })} muted />
|
||||
</div>
|
||||
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(2, minmax(0, 1fr))', gap: 10 }} className="automation-inventory-capability-grid">
|
||||
{visibleDelegableCapabilities.map(capability => (
|
||||
<div key={capability.capability_id} style={{ padding: 11, border: '0.5px solid #eee9dd', borderRadius: 7, background: '#faf9f3', display: 'flex', flexDirection: 'column', gap: 8, minWidth: 0 }}>
|
||||
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 8, minWidth: 0 }}>
|
||||
<span style={{ fontFamily: "'DM Mono', monospace", fontSize: 11, fontWeight: 700, color: '#141413', overflowWrap: 'anywhere' }}>
|
||||
{capability.capability_id}
|
||||
</span>
|
||||
<Chip value={proactiveValueLabel('risks', capability.risk_tier)} muted={capability.risk_tier === 'low'} />
|
||||
</div>
|
||||
<span style={{ fontFamily: 'Syne, sans-serif', fontSize: 12, fontWeight: 700, color: '#141413', lineHeight: 1.35 }}>
|
||||
{capability.display_name}
|
||||
</span>
|
||||
<div style={{ display: 'flex', flexWrap: 'wrap', gap: 6, minWidth: 0 }}>
|
||||
<Chip value={`${t('proactiveOperations.labels.owner')}: ${proactiveValueLabel('agents', capability.primary_owner)}`} />
|
||||
<Chip value={`${t('proactiveOperations.labels.level')}: ${proactiveValueLabel('autonomyLevels', capability.automation_level)}`} muted />
|
||||
<Chip value={`${t('proactiveOperations.labels.gate')}: ${capability.approval_gate}`} muted={capability.approval_gate === 'read_only_allowed'} />
|
||||
<Chip value={`${t('proactiveOperations.labels.outputs')}: ${capability.outputs.length}`} muted />
|
||||
</div>
|
||||
<SmallLabel>{t('proactiveOperations.labels.telegram')}</SmallLabel>
|
||||
<span style={{ fontFamily: "'DM Mono', monospace", fontSize: 10, color: '#87867f', lineHeight: 1.45, overflowWrap: 'anywhere' }}>
|
||||
{capability.telegram_policy}
|
||||
</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style={{ padding: 12, border: '0.5px solid #e0ddd4', borderRadius: 7, background: '#fff', display: 'flex', flexDirection: 'column', gap: 10, minWidth: 0 }}>
|
||||
<SmallLabel>{t('proactiveOperations.domainsTitle')}</SmallLabel>
|
||||
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(2, minmax(0, 1fr))', gap: 10 }} className="automation-inventory-domain-grid">
|
||||
{visibleVersionDomains.map(domain => (
|
||||
<div key={domain.domain_id} style={{ padding: 10, border: '0.5px solid #eee9dd', borderRadius: 7, background: '#faf9f3', display: 'flex', flexDirection: 'column', gap: 7, minWidth: 0 }}>
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between', gap: 8, alignItems: 'center', minWidth: 0 }}>
|
||||
<span style={{ fontFamily: 'Syne, sans-serif', fontSize: 12, fontWeight: 700, color: '#141413', overflowWrap: 'anywhere' }}>
|
||||
{domain.display_name}
|
||||
</span>
|
||||
<Chip value={proactiveValueLabel('agents', domain.primary_owner)} muted />
|
||||
</div>
|
||||
<div style={{ display: 'flex', flexWrap: 'wrap', gap: 6 }}>
|
||||
<Chip value={`${t('proactiveOperations.labels.cadence')}: ${domain.cadence}`} muted />
|
||||
<Chip value={`${t('proactiveOperations.labels.level')}: ${proactiveValueLabel('autonomyLevels', domain.current_allowed_autonomy)}`} muted />
|
||||
<Chip value={`${t('proactiveOperations.labels.examples')}: ${domain.tracked_examples.length}`} muted />
|
||||
</div>
|
||||
<span style={{ fontFamily: "'DM Mono', monospace", fontSize: 10, color: '#87867f', lineHeight: 1.45, overflowWrap: 'anywhere' }}>
|
||||
{domain.approval_gate}
|
||||
</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 12, minWidth: 0 }}>
|
||||
<div style={{ padding: 12, border: '0.5px solid #e0ddd4', borderRadius: 7, background: '#fff', display: 'flex', flexDirection: 'column', gap: 10, minWidth: 0 }}>
|
||||
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 10, flexWrap: 'wrap' }}>
|
||||
<span style={{ fontFamily: 'Syne, sans-serif', fontSize: 13, fontWeight: 700, color: '#141413' }}>
|
||||
{t('hostStateful.hostListTitle')}
|
||||
</span>
|
||||
<Chip value={t('hostStateful.source', {
|
||||
generated: formatDateTime(hostStatefulInventory.generated_at),
|
||||
current: hostStatefulInventory.program_status.current_task_id,
|
||||
next: hostStatefulInventory.program_status.next_task_id,
|
||||
})} muted />
|
||||
</div>
|
||||
{visibleHostInventory.map(host => (
|
||||
<div key={host.host_id} style={{ padding: 10, border: '0.5px solid #eee9dd', borderRadius: 7, background: '#faf9f3', display: 'flex', flexDirection: 'column', gap: 7, minWidth: 0 }}>
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between', gap: 8, alignItems: 'center', minWidth: 0 }}>
|
||||
<span style={{ fontFamily: "'DM Mono', monospace", fontSize: 11, fontWeight: 700, color: '#141413', overflowWrap: 'anywhere' }}>
|
||||
{host.host_id}
|
||||
</span>
|
||||
<Chip value={hostStatefulValueLabel('values', host.version_observation_status)} muted />
|
||||
</div>
|
||||
<span style={{ fontFamily: 'Syne, sans-serif', fontSize: 12, fontWeight: 700, color: '#141413', lineHeight: 1.35 }}>
|
||||
{host.display_name}
|
||||
</span>
|
||||
<div style={{ display: 'flex', flexWrap: 'wrap', gap: 6 }}>
|
||||
<Chip value={`${t('hostStateful.labels.surfaces')}: ${host.primary_surfaces.length}`} muted />
|
||||
<Chip value={`${t('hostStateful.labels.evidence')}: ${host.next_evidence_needed.length}`} muted />
|
||||
<Chip value={`${t('hostStateful.labels.blocked')}: ${host.blocked_actions.length}`} muted={host.blocked_actions.length === 0} />
|
||||
<Chip value={host.maintenance_window_required ? t('hostStateful.labels.maintenanceRequired') : t('hostStateful.labels.maintenanceNotRequired')} />
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div style={{ padding: 12, border: '0.5px solid #e0ddd4', borderRadius: 7, background: '#fff', display: 'flex', flexDirection: 'column', gap: 10, minWidth: 0 }}>
|
||||
<SmallLabel>{t('hostStateful.serviceListTitle')}</SmallLabel>
|
||||
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(2, minmax(0, 1fr))', gap: 8 }} className="automation-inventory-stateful-grid">
|
||||
{visibleStatefulServices.map(service => (
|
||||
<div key={service.service_id} style={{ padding: 9, border: '0.5px solid #eee9dd', borderRadius: 7, background: '#faf9f3', display: 'flex', flexDirection: 'column', gap: 6, minWidth: 0 }}>
|
||||
<span style={{ fontFamily: 'Syne, sans-serif', fontSize: 12, fontWeight: 700, color: '#141413' }}>
|
||||
{service.display_name}
|
||||
</span>
|
||||
<div style={{ display: 'flex', flexWrap: 'wrap', gap: 6 }}>
|
||||
<Chip value={service.service_id} muted />
|
||||
<Chip value={service.backup_required_before_change ? t('hostStateful.labels.backupRequired') : t('hostStateful.labels.backupNotRequired')} />
|
||||
<Chip value={`${t('hostStateful.labels.restart')}: ${String(service.restart_authorized)}`} muted />
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style={{ padding: 12, border: '0.5px solid #e0ddd4', borderRadius: 7, background: '#fff', display: 'flex', flexDirection: 'column', gap: 8, minWidth: 0 }}>
|
||||
<SmallLabel>{t('hostStateful.maintenanceTitle')}</SmallLabel>
|
||||
<div style={{ display: 'flex', flexWrap: 'wrap', gap: 6 }}>
|
||||
<Chip value={`${t('hostStateful.labels.requiredFields')}: ${hostStatefulInventory.maintenance_window_approval_package.required_fields.length}`} />
|
||||
<Chip value={`${t('hostStateful.labels.smoke')}: ${hostStatefulInventory.maintenance_window_approval_package.minimum_smoke_plan.length}`} muted />
|
||||
<Chip value={`${t('hostStateful.labels.forbiddenFields')}: ${hostStatefulInventory.maintenance_window_approval_package.forbidden_fields.length}`} muted />
|
||||
<Chip value={`${t('hostStateful.labels.probeSteps')}: ${hostStatefulInventory.readonly_probe_plan.length}`} muted />
|
||||
</div>
|
||||
<div style={{ display: 'flex', flexWrap: 'wrap', gap: 6 }}>
|
||||
<Chip value={`${t('hostStateful.labels.telegramDirect')}: ${hostTelegramDirectSendAllowed}`} muted />
|
||||
<Chip value={`${t('hostStateful.labels.telegramQueue')}: ${hostTelegramGatewayWriteAllowed}`} muted />
|
||||
<Chip value={hostRedactionLocked ? t('hostStateful.labels.redactionLocked') : t('hostStateful.labels.redactionReview')} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</GlassCard>
|
||||
|
||||
<GlassCard variant="subtle" padding="md">
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 13, minWidth: 0 }}>
|
||||
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 12, flexWrap: 'wrap' }}>
|
||||
|
||||
Reference in New Issue
Block a user