Add View in CARD button to tooltip and action modal
Copies the Ivanti Host ID to clipboard and opens card.charter.com/ipn-search in a new tab. ESSO prevents URL-based pre-fill so the user pastes the ID.
This commit is contained in:
@@ -6,7 +6,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import React, { useState, useEffect } from 'react';
|
import React, { useState, useEffect } from 'react';
|
||||||
import { X, Loader, AlertCircle, CheckCircle, ArrowRightLeft, XCircle } from 'lucide-react'; // ⚠️ CONVENTION: Removed unused `Shield` import to satisfy no-unused-vars lint rule
|
import { X, Loader, AlertCircle, CheckCircle, ArrowRightLeft, XCircle, ExternalLink } from 'lucide-react';
|
||||||
|
|
||||||
const API_BASE = process.env.REACT_APP_API_BASE || 'http://localhost:3001/api';
|
const API_BASE = process.env.REACT_APP_API_BASE || 'http://localhost:3001/api';
|
||||||
|
|
||||||
@@ -148,8 +148,36 @@ export default function CardActionModal({ isOpen, onClose, item, initialAction,
|
|||||||
<div>
|
<div>
|
||||||
<h3 style={{ margin: 0, color: '#F8FAFC', fontSize: '0.95rem' }}>CARD Asset Action</h3>
|
<h3 style={{ margin: 0, color: '#F8FAFC', fontSize: '0.95rem' }}>CARD Asset Action</h3>
|
||||||
{ownerData && (
|
{ownerData && (
|
||||||
<div style={{ fontSize: '0.7rem', color: '#7C3AED', fontFamily: 'monospace', marginTop: '0.2rem' }}>
|
<div style={{ display: 'flex', alignItems: 'center', gap: '0.5rem', marginTop: '0.2rem' }}>
|
||||||
{ownerData.asset_id}
|
<span style={{ fontSize: '0.7rem', color: '#7C3AED', fontFamily: 'monospace' }}>
|
||||||
|
{ownerData.asset_id}
|
||||||
|
</span>
|
||||||
|
{item?.host_id && (
|
||||||
|
<button
|
||||||
|
onClick={() => {
|
||||||
|
navigator.clipboard.writeText(String(item.host_id));
|
||||||
|
window.open('https://card.charter.com/ipn-search', '_blank');
|
||||||
|
}}
|
||||||
|
title={`Copy Host ID ${item.host_id} and open CARD`}
|
||||||
|
style={{
|
||||||
|
display: 'inline-flex', alignItems: 'center', gap: '0.25rem',
|
||||||
|
padding: '0.15rem 0.45rem',
|
||||||
|
background: 'rgba(14, 165, 233, 0.12)',
|
||||||
|
border: '1px solid rgba(14, 165, 233, 0.4)',
|
||||||
|
borderRadius: '0.25rem',
|
||||||
|
color: '#7DD3FC',
|
||||||
|
fontSize: '0.58rem', fontWeight: '600', fontFamily: 'monospace',
|
||||||
|
cursor: 'pointer',
|
||||||
|
textTransform: 'uppercase', letterSpacing: '0.04em',
|
||||||
|
transition: 'all 0.12s',
|
||||||
|
}}
|
||||||
|
onMouseEnter={(e) => { e.currentTarget.style.background = 'rgba(14, 165, 233, 0.25)'; }}
|
||||||
|
onMouseLeave={(e) => { e.currentTarget.style.background = 'rgba(14, 165, 233, 0.12)'; }}
|
||||||
|
>
|
||||||
|
<ExternalLink style={{ width: 9, height: 9 }} />
|
||||||
|
CARD
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -9,10 +9,9 @@
|
|||||||
|
|
||||||
import React, { useState, useEffect, useRef, useLayoutEffect, useCallback } from 'react';
|
import React, { useState, useEffect, useRef, useLayoutEffect, useCallback } from 'react';
|
||||||
import ReactDOM from 'react-dom';
|
import ReactDOM from 'react-dom';
|
||||||
import { Loader, AlertCircle, ExternalLink } from 'lucide-react';
|
import { Loader, AlertCircle, ExternalLink, Copy } from 'lucide-react';
|
||||||
|
|
||||||
// ⚠️ CONVENTION: Use relative API path from REACT_APP_API_BASE only (no absolute URL fallback).
|
// ⚠️ CONVENTION: Use relative API path fallback, not absolute URL. Should be: process.env.REACT_APP_API_BASE || '/api'
|
||||||
// Other components use: const API_BASE = process.env.REACT_APP_API_BASE || '/api';
|
|
||||||
const API_BASE = process.env.REACT_APP_API_BASE || 'http://localhost:3001/api';
|
const API_BASE = process.env.REACT_APP_API_BASE || 'http://localhost:3001/api';
|
||||||
|
|
||||||
const TOOLTIP_GAP = 8;
|
const TOOLTIP_GAP = 8;
|
||||||
@@ -136,6 +135,7 @@ export default function CardOwnerTooltip({ ip, hostId, anchorRect, cache, cardCo
|
|||||||
error={error}
|
error={error}
|
||||||
anchorRect={anchorRect}
|
anchorRect={anchorRect}
|
||||||
ip={ip}
|
ip={ip}
|
||||||
|
hostId={hostId}
|
||||||
onAction={onAction}
|
onAction={onAction}
|
||||||
onMouseEnter={onMouseEnter}
|
onMouseEnter={onMouseEnter}
|
||||||
onMouseLeave={onMouseLeave}
|
onMouseLeave={onMouseLeave}
|
||||||
@@ -147,7 +147,7 @@ export default function CardOwnerTooltip({ ip, hostId, anchorRect, cache, cardCo
|
|||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
// TooltipBody — inner component for measurement + rendering
|
// TooltipBody — inner component for measurement + rendering
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
function TooltipBody({ data, loading, error, anchorRect, ip, onAction, onMouseEnter, onMouseLeave }) {
|
function TooltipBody({ data, loading, error, anchorRect, ip, hostId, onAction, onMouseEnter, onMouseLeave }) {
|
||||||
const tooltipRef = useRef(null);
|
const tooltipRef = useRef(null);
|
||||||
const [pos, setPos] = useState({ top: 0, left: 0, placeAbove: true });
|
const [pos, setPos] = useState({ top: 0, left: 0, placeAbove: true });
|
||||||
|
|
||||||
@@ -304,7 +304,7 @@ function TooltipBody({ data, loading, error, anchorRect, ip, onAction, onMouseEn
|
|||||||
|
|
||||||
{/* Actions button */}
|
{/* Actions button */}
|
||||||
{onAction && (
|
{onAction && (
|
||||||
<div style={{ marginTop: '0.4rem', paddingTop: '0.4rem', borderTop: '1px solid rgba(124, 58, 237, 0.2)' }}>
|
<div style={{ marginTop: '0.4rem', paddingTop: '0.4rem', borderTop: '1px solid rgba(124, 58, 237, 0.2)', display: 'flex', gap: '0.4rem', flexWrap: 'wrap' }}>
|
||||||
<button
|
<button
|
||||||
onClick={handleAction}
|
onClick={handleAction}
|
||||||
style={{
|
style={{
|
||||||
@@ -325,6 +325,32 @@ function TooltipBody({ data, loading, error, anchorRect, ip, onAction, onMouseEn
|
|||||||
<ExternalLink style={{ width: 11, height: 11 }} />
|
<ExternalLink style={{ width: 11, height: 11 }} />
|
||||||
Actions
|
Actions
|
||||||
</button>
|
</button>
|
||||||
|
{hostId && (
|
||||||
|
<button
|
||||||
|
onClick={() => {
|
||||||
|
navigator.clipboard.writeText(String(hostId));
|
||||||
|
window.open('https://card.charter.com/ipn-search', '_blank');
|
||||||
|
}}
|
||||||
|
title={`Copy Host ID ${hostId} and open CARD`}
|
||||||
|
style={{
|
||||||
|
display: 'inline-flex', alignItems: 'center', gap: '0.35rem',
|
||||||
|
padding: '0.3rem 0.65rem',
|
||||||
|
background: 'rgba(14, 165, 233, 0.12)',
|
||||||
|
border: '1px solid rgba(14, 165, 233, 0.4)',
|
||||||
|
borderRadius: '0.3rem',
|
||||||
|
color: '#7DD3FC',
|
||||||
|
fontSize: '0.65rem', fontWeight: '600', fontFamily: 'monospace',
|
||||||
|
cursor: 'pointer',
|
||||||
|
textTransform: 'uppercase', letterSpacing: '0.04em',
|
||||||
|
transition: 'all 0.12s',
|
||||||
|
}}
|
||||||
|
onMouseEnter={(e) => { e.currentTarget.style.background = 'rgba(14, 165, 233, 0.25)'; e.currentTarget.style.borderColor = 'rgba(14, 165, 233, 0.6)'; }}
|
||||||
|
onMouseLeave={(e) => { e.currentTarget.style.background = 'rgba(14, 165, 233, 0.12)'; e.currentTarget.style.borderColor = 'rgba(14, 165, 233, 0.4)'; }}
|
||||||
|
>
|
||||||
|
<Copy style={{ width: 11, height: 11 }} />
|
||||||
|
View in CARD
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user