style(headers): update all page header titles and add HelpTrigger buttons matching demoplace.my.id
This commit is contained in:
1 parent
fb75711994
commit
36fe496566
20 files changed
+490
-18
No files matched your search
@@ -0,0 +1,66 @@
|
|||||||
|
const { Client } = require('ssh2');
|
||||||
|
|
||||||
|
const remoteScript = `
|
||||||
|
const fs = require('fs');
|
||||||
|
const path = require('path');
|
||||||
|
|
||||||
|
const root = '/home/adminbackend/web/demoplace.my.id/public_html/.next/standalone/.next/server/app/(dashboard)';
|
||||||
|
|
||||||
|
function scanDir(dir) {
|
||||||
|
const results = [];
|
||||||
|
if (!fs.existsSync(dir)) return results;
|
||||||
|
const items = fs.readdirSync(dir, { withFileTypes: true });
|
||||||
|
for (const item of items) {
|
||||||
|
const fullPath = path.join(dir, item.name);
|
||||||
|
if (item.isDirectory()) {
|
||||||
|
results.push(...scanDir(fullPath));
|
||||||
|
} else if (item.name.endsWith('.html') || item.name.endsWith('.js')) {
|
||||||
|
results.push(fullPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('=== AUDITING ALL PAGE HEADERS AND TITLES ON DEMOPLACE.MY.ID ===\\n');
|
||||||
|
|
||||||
|
const files = scanDir(root);
|
||||||
|
files.forEach(file => {
|
||||||
|
const content = fs.readFileSync(file, 'utf8');
|
||||||
|
const rel = path.relative(root, file);
|
||||||
|
|
||||||
|
// Extract <h2> or <h1> or header titles
|
||||||
|
const h2s = [];
|
||||||
|
const h2Match = content.matchAll(/<h2[^>]*>(.*?)<\/h2>/gi);
|
||||||
|
for (const m of h2Match) {
|
||||||
|
const clean = m[1].replace(/<[^>]+>/g, '').trim();
|
||||||
|
if (clean && !h2s.includes(clean)) h2s.push(clean);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract Learn This Page triggers
|
||||||
|
const hasHelpTrigger = content.includes('Learn This Page');
|
||||||
|
|
||||||
|
if (h2s.length > 0 || hasHelpTrigger) {
|
||||||
|
console.log('Page Route File:', rel);
|
||||||
|
if (h2s.length > 0) console.log(' Headers (H2):', h2s.join(' | '));
|
||||||
|
console.log(' Has Learn This Page Button:', hasHelpTrigger);
|
||||||
|
console.log('---');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
`;
|
||||||
|
|
||||||
|
const conn = new Client();
|
||||||
|
conn.on('ready', () => {
|
||||||
|
conn.exec(`node -e ${JSON.stringify(remoteScript)}`, (err, stream) => {
|
||||||
|
let out = '';
|
||||||
|
stream.on('data', d => out += d);
|
||||||
|
stream.on('close', () => {
|
||||||
|
console.log(out);
|
||||||
|
conn.end();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}).connect({
|
||||||
|
host: '103.185.47.52',
|
||||||
|
port: 2222,
|
||||||
|
username: 'adminbackend',
|
||||||
|
password: 'htEo7x6LsBQiEHHH'
|
||||||
|
});
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
const SftpClient = require('ssh2-sftp-client');
|
||||||
|
const { Client } = require('ssh2');
|
||||||
|
|
||||||
|
const remoteExtractScript = `
|
||||||
|
const fs = require('fs');
|
||||||
|
const path = require('path');
|
||||||
|
|
||||||
|
const root = '/home/adminbackend/web/demoplace.my.id/public_html/.next/standalone/.next/server/chunks';
|
||||||
|
|
||||||
|
console.log('=== EXTRACTING ALL PAGE HEADERS FROM CHUNKS ===\\n');
|
||||||
|
|
||||||
|
const files = fs.readdirSync(root);
|
||||||
|
files.forEach(f => {
|
||||||
|
if (!f.endsWith('.js')) return;
|
||||||
|
const content = fs.readFileSync(path.join(root, f), 'utf8');
|
||||||
|
|
||||||
|
// Match header titles in React JSX: e.g. text-3xl font-bold ... >TitleText<
|
||||||
|
const titleMatches = content.matchAll(/text-3xl[^>]*>([^<]{3,40})</gi);
|
||||||
|
for (const m of titleMatches) {
|
||||||
|
console.log(\`Chunk \${f}: "\${m[1].trim()}"\`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Match tab names in React JSX
|
||||||
|
const tabMatches = content.matchAll(/role="tab"[^>]*>([^<]{3,30})</gi);
|
||||||
|
for (const m of tabMatches) {
|
||||||
|
console.log(\`Chunk \${f} Tab: "\${m[1].trim()}"\`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
`;
|
||||||
|
|
||||||
|
async function main() {
|
||||||
|
const sftp = new SftpClient();
|
||||||
|
await sftp.connect({
|
||||||
|
host: '103.185.47.52',
|
||||||
|
port: 2222,
|
||||||
|
username: 'adminbackend',
|
||||||
|
password: 'htEo7x6LsBQiEHHH'
|
||||||
|
});
|
||||||
|
|
||||||
|
const remoteScriptPath = '/home/adminbackend/extract_client_titles.js';
|
||||||
|
await sftp.put(Buffer.from(remoteExtractScript), remoteScriptPath);
|
||||||
|
await sftp.end();
|
||||||
|
|
||||||
|
const conn = new Client();
|
||||||
|
conn.on('ready', () => {
|
||||||
|
conn.exec(`node ${remoteScriptPath}`, (err, stream) => {
|
||||||
|
let out = '';
|
||||||
|
stream.on('data', d => out += d);
|
||||||
|
stream.on('close', () => {
|
||||||
|
console.log(out);
|
||||||
|
conn.end();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}).connect({
|
||||||
|
host: '103.185.47.52',
|
||||||
|
port: 2222,
|
||||||
|
username: 'adminbackend',
|
||||||
|
password: 'htEo7x6LsBQiEHHH'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
main();
|
||||||
@@ -0,0 +1,69 @@
|
|||||||
|
const SftpClient = require('ssh2-sftp-client');
|
||||||
|
const { Client } = require('ssh2');
|
||||||
|
|
||||||
|
const remoteExtractScript = `
|
||||||
|
const fs = require('fs');
|
||||||
|
const path = require('path');
|
||||||
|
|
||||||
|
const root = '/home/adminbackend/web/demoplace.my.id/public_html/.next/standalone/.next/server';
|
||||||
|
|
||||||
|
console.log('=== EXTRACTING ALL PAGE HEADERS FROM STANDALONE BUILD ===\\n');
|
||||||
|
|
||||||
|
function scan(dir) {
|
||||||
|
if (!fs.existsSync(dir)) return;
|
||||||
|
const items = fs.readdirSync(dir, { withFileTypes: true });
|
||||||
|
for (const item of items) {
|
||||||
|
const full = path.join(dir, item.name);
|
||||||
|
if (item.isDirectory()) {
|
||||||
|
scan(full);
|
||||||
|
} else if (item.name.endsWith('.js') || item.name.endsWith('.html')) {
|
||||||
|
const content = fs.readFileSync(full, 'utf8');
|
||||||
|
|
||||||
|
// Look for H2/H1 page header titles or page metadata
|
||||||
|
const matches = content.matchAll(/<h[12][^>]*>([\\s\\S]*?)<\\/h[12]>/gi);
|
||||||
|
for (const m of matches) {
|
||||||
|
const clean = m[1].replace(/<[^>]+>/g, '').trim();
|
||||||
|
if (clean && clean.length > 2 && clean.length < 60 && !clean.includes('{') && !clean.includes('function')) {
|
||||||
|
console.log(\`File: \${path.relative(root, full)}\`);
|
||||||
|
console.log(\` Header: "\${clean}"\`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
scan(path.join(root, 'app/(dashboard)'));
|
||||||
|
`;
|
||||||
|
|
||||||
|
async function main() {
|
||||||
|
const sftp = new SftpClient();
|
||||||
|
await sftp.connect({
|
||||||
|
host: '103.185.47.52',
|
||||||
|
port: 2222,
|
||||||
|
username: 'adminbackend',
|
||||||
|
password: 'htEo7x6LsBQiEHHH'
|
||||||
|
});
|
||||||
|
|
||||||
|
const remoteScriptPath = '/home/adminbackend/extract_standalone.js';
|
||||||
|
await sftp.put(Buffer.from(remoteExtractScript), remoteScriptPath);
|
||||||
|
await sftp.end();
|
||||||
|
|
||||||
|
const conn = new Client();
|
||||||
|
conn.on('ready', () => {
|
||||||
|
conn.exec(`node ${remoteScriptPath}`, (err, stream) => {
|
||||||
|
let out = '';
|
||||||
|
stream.on('data', d => out += d);
|
||||||
|
stream.on('close', () => {
|
||||||
|
console.log(out);
|
||||||
|
conn.end();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}).connect({
|
||||||
|
host: '103.185.47.52',
|
||||||
|
port: 2222,
|
||||||
|
username: 'adminbackend',
|
||||||
|
password: 'htEo7x6LsBQiEHHH'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
main();
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
const { Client } = require('ssh2');
|
||||||
|
|
||||||
|
const conn = new Client();
|
||||||
|
conn.on('ready', () => {
|
||||||
|
// Execute a shell script on remote server
|
||||||
|
conn.exec(`python3 -c "
|
||||||
|
import os, re
|
||||||
|
|
||||||
|
root = '/home/adminbackend/web/demoplace.my.id/public_html/.next/standalone/.next/server/app/(dashboard)'
|
||||||
|
|
||||||
|
for dirpath, dirnames, filenames in os.walk(root):
|
||||||
|
for f in filenames:
|
||||||
|
if f.endswith('.html') or f.endswith('.js'):
|
||||||
|
full = os.path.join(dirpath, f)
|
||||||
|
rel = os.path.relpath(full, root)
|
||||||
|
try:
|
||||||
|
content = open(full, 'r', encoding='utf-8', errors='ignore').read()
|
||||||
|
|
||||||
|
h2s = re.findall(r'<h[12][^>]*>(.*?)</h[12]>', content)
|
||||||
|
clean_h2s = [re.sub(r'<[^>]+>', '', h).strip() for h in h2s if h.strip()]
|
||||||
|
|
||||||
|
has_learn = 'Learn This Page' in content
|
||||||
|
|
||||||
|
if clean_h2s or has_learn:
|
||||||
|
print(f'Route File: {rel}')
|
||||||
|
print(f' Headers: {\" | \".join(clean_h2s)}')
|
||||||
|
print(f' Learn This Page Button: {has_learn}')
|
||||||
|
print('-' * 40)
|
||||||
|
except Exception as e:
|
||||||
|
pass
|
||||||
|
"`, (err, stream) => {
|
||||||
|
let out = '';
|
||||||
|
stream.on('data', d => out += d);
|
||||||
|
stream.on('close', () => {
|
||||||
|
console.log(out);
|
||||||
|
conn.end();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}).connect({
|
||||||
|
host: '103.185.47.52',
|
||||||
|
port: 2222,
|
||||||
|
username: 'adminbackend',
|
||||||
|
password: 'htEo7x6LsBQiEHHH'
|
||||||
|
});
|
||||||
@@ -0,0 +1,80 @@
|
|||||||
|
const SftpClient = require('ssh2-sftp-client');
|
||||||
|
const { Client } = require('ssh2');
|
||||||
|
|
||||||
|
const remoteScanScript = `
|
||||||
|
const fs = require('fs');
|
||||||
|
const path = require('path');
|
||||||
|
|
||||||
|
const root = '/home/adminbackend/web/demoplace.my.id/public_html/.next';
|
||||||
|
|
||||||
|
const targetTitles = [
|
||||||
|
'Overview Dashboard',
|
||||||
|
'Summary Overview',
|
||||||
|
'DPI MetaData',
|
||||||
|
'BackOne Metadata',
|
||||||
|
'Network Topology',
|
||||||
|
'Network Infrastructure',
|
||||||
|
'Traffic Categories',
|
||||||
|
'Threat Intelligence',
|
||||||
|
'Detected Threats',
|
||||||
|
'App Lookup',
|
||||||
|
'Application Database',
|
||||||
|
'Encryption Audit',
|
||||||
|
'Security & Encryption Audit'
|
||||||
|
];
|
||||||
|
|
||||||
|
console.log('=== MATCHING TARGET TITLES IN REMOTE NEXT BUILD ===\\n');
|
||||||
|
|
||||||
|
function scan(dir) {
|
||||||
|
if (!fs.existsSync(dir)) return;
|
||||||
|
const items = fs.readdirSync(dir, { withFileTypes: true });
|
||||||
|
for (const item of items) {
|
||||||
|
const full = path.join(dir, item.name);
|
||||||
|
if (item.isDirectory()) {
|
||||||
|
scan(full);
|
||||||
|
} else if (item.name.endsWith('.js') || item.name.endsWith('.html')) {
|
||||||
|
const content = fs.readFileSync(full, 'utf8');
|
||||||
|
targetTitles.forEach(t => {
|
||||||
|
if (content.includes(t)) {
|
||||||
|
console.log(\`Found "\${t}" in file: \${path.relative(root, full)}\`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
scan(root);
|
||||||
|
`;
|
||||||
|
|
||||||
|
async function main() {
|
||||||
|
const sftp = new SftpClient();
|
||||||
|
await sftp.connect({
|
||||||
|
host: '103.185.47.52',
|
||||||
|
port: 2222,
|
||||||
|
username: 'adminbackend',
|
||||||
|
password: 'htEo7x6LsBQiEHHH'
|
||||||
|
});
|
||||||
|
|
||||||
|
const remoteScriptPath = '/home/adminbackend/scan_strings.js';
|
||||||
|
await sftp.put(Buffer.from(remoteScanScript), remoteScriptPath);
|
||||||
|
await sftp.end();
|
||||||
|
|
||||||
|
const conn = new Client();
|
||||||
|
conn.on('ready', () => {
|
||||||
|
conn.exec(`node ${remoteScriptPath}`, (err, stream) => {
|
||||||
|
let out = '';
|
||||||
|
stream.on('data', d => out += d);
|
||||||
|
stream.on('close', () => {
|
||||||
|
console.log(out);
|
||||||
|
conn.end();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}).connect({
|
||||||
|
host: '103.185.47.52',
|
||||||
|
port: 2222,
|
||||||
|
username: 'adminbackend',
|
||||||
|
password: 'htEo7x6LsBQiEHHH'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
main();
|
||||||
@@ -0,0 +1,94 @@
|
|||||||
|
const SftpClient = require('ssh2-sftp-client');
|
||||||
|
const { Client } = require('ssh2');
|
||||||
|
|
||||||
|
const remoteAuditScript = `
|
||||||
|
const fs = require('fs');
|
||||||
|
const path = require('path');
|
||||||
|
|
||||||
|
const root = '/home/adminbackend/web/demoplace.my.id/public_html';
|
||||||
|
|
||||||
|
console.log('====================================================');
|
||||||
|
console.log(' FULL COMPONENT & PAGE AUDIT OF DEMOPLACE.MY.ID ');
|
||||||
|
console.log('====================================================');
|
||||||
|
|
||||||
|
// 1. Audit Client/Server Chunks for Page Headers & HelpTriggers
|
||||||
|
const chunksDir = path.join(root, '.next/standalone/.next/server/chunks');
|
||||||
|
if (fs.existsSync(chunksDir)) {
|
||||||
|
const files = fs.readdirSync(chunksDir);
|
||||||
|
files.forEach(f => {
|
||||||
|
if (!f.endsWith('.js')) return;
|
||||||
|
const content = fs.readFileSync(path.join(chunksDir, f), 'utf8');
|
||||||
|
|
||||||
|
// Look for page headers or title definitions
|
||||||
|
const headerMatch = content.match(/["']([A-Z][A-Za-z0-9\\s]{3,35})["'],\\s*description:/);
|
||||||
|
if (headerMatch) {
|
||||||
|
console.log(\`[Chunk \${f}] Found Header Definition: "\${headerMatch[1]}"\`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (content.includes('Learn This Page')) {
|
||||||
|
console.log(\`[Chunk \${f}] Contains "Learn This Page" button\`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Audit Page Source files in src/app/(dashboard)
|
||||||
|
const appSrcDir = path.join(root, 'src/app/(dashboard)');
|
||||||
|
if (fs.existsSync(appSrcDir)) {
|
||||||
|
function scan(dir) {
|
||||||
|
const items = fs.readdirSync(dir, { withFileTypes: true });
|
||||||
|
for (const item of items) {
|
||||||
|
const full = path.join(dir, item.name);
|
||||||
|
if (item.isDirectory()) {
|
||||||
|
scan(full);
|
||||||
|
} else if (item.name === 'page.tsx' || item.name.endsWith('.tsx')) {
|
||||||
|
const content = fs.readFileSync(full, 'utf8');
|
||||||
|
const rel = path.relative(appSrcDir, full);
|
||||||
|
|
||||||
|
// Find H2 or H1
|
||||||
|
const h2 = content.match(/<h[12][^>]*>([\\s\\S]*?)<\\/h[12]>/i);
|
||||||
|
const titleText = h2 ? h2[1].replace(/<[^>]+>/g, '').trim().split('\\n')[0] : 'None';
|
||||||
|
const hasHelp = content.includes('HelpTrigger') || content.includes('Learn This Page');
|
||||||
|
|
||||||
|
console.log(\`Page File: \${rel}\`);
|
||||||
|
console.log(\` Header Title : \${titleText}\`);
|
||||||
|
console.log(\` Has HelpButton: \${hasHelp}\`);
|
||||||
|
console.log('----------------------------------------------------');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
scan(appSrcDir);
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
async function main() {
|
||||||
|
const sftp = new SftpClient();
|
||||||
|
await sftp.connect({
|
||||||
|
host: '103.185.47.52',
|
||||||
|
port: 2222,
|
||||||
|
username: 'adminbackend',
|
||||||
|
password: 'htEo7x6LsBQiEHHH'
|
||||||
|
});
|
||||||
|
|
||||||
|
const remoteScriptPath = '/home/adminbackend/audit_remote.js';
|
||||||
|
await sftp.put(Buffer.from(remoteAuditScript), remoteScriptPath);
|
||||||
|
await sftp.end();
|
||||||
|
|
||||||
|
const conn = new Client();
|
||||||
|
conn.on('ready', () => {
|
||||||
|
conn.exec(`node ${remoteScriptPath}`, (err, stream) => {
|
||||||
|
let out = '';
|
||||||
|
stream.on('data', d => out += d);
|
||||||
|
stream.on('close', () => {
|
||||||
|
console.log(out);
|
||||||
|
conn.end();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}).connect({
|
||||||
|
host: '103.185.47.52',
|
||||||
|
port: 2222,
|
||||||
|
username: 'adminbackend',
|
||||||
|
password: 'htEo7x6LsBQiEHHH'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
main();
|
||||||
@@ -14,6 +14,7 @@ import { usePollingKey } from "@/lib/usePolling";
|
|||||||
import { AgentModals } from "@/components/admin/AgentModals";
|
import { AgentModals } from "@/components/admin/AgentModals";
|
||||||
import ExternalAccountModal from "@/components/admin/ExternalAccountModal";
|
import ExternalAccountModal from "@/components/admin/ExternalAccountModal";
|
||||||
import { getAgentColumns, getExternalAccountColumns } from "./columns";
|
import { getAgentColumns, getExternalAccountColumns } from "./columns";
|
||||||
|
import { HelpTrigger } from "@/components/help/HelpTrigger";
|
||||||
|
|
||||||
export default function AgentsPage() {
|
export default function AgentsPage() {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
@@ -175,11 +176,14 @@ export default function AgentsPage() {
|
|||||||
return (
|
return (
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<h2 className="text-2xl font-bold tracking-tight text-white flex items-center gap-3">
|
<div>
|
||||||
<Server className="w-6 h-6 text-primary" />
|
<h2 className="text-3xl font-bold tracking-tight text-white flex items-center gap-3">
|
||||||
Agents Inventory
|
Agents Inventory
|
||||||
{isLoading && <Loader2 className="w-5 h-5 animate-spin text-muted-foreground" />}
|
{isLoading && <Loader2 className="w-5 h-5 animate-spin text-muted-foreground" />}
|
||||||
</h2>
|
</h2>
|
||||||
|
<p className="text-muted-foreground mt-1">Managed DPI probes, remote collectors, and probe infrastructure.</p>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-3">
|
||||||
{(role === 'SUPER_ADMIN' || role === 'TENANT_ADMIN') && (
|
{(role === 'SUPER_ADMIN' || role === 'TENANT_ADMIN') && (
|
||||||
<button
|
<button
|
||||||
onClick={() => setIsCreateOpen(true)}
|
onClick={() => setIsCreateOpen(true)}
|
||||||
@@ -189,6 +193,8 @@ export default function AgentsPage() {
|
|||||||
Provision Agent
|
Provision Agent
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
|
<HelpTrigger pageId="agents" />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{error && (
|
{error && (
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import { Loader2, ChevronRight, Globe } from "lucide-react";
|
|||||||
import { UniversalFilters, useUniversalFilterState, FilterConfig } from "@/components/ui/UniversalFilters";
|
import { UniversalFilters, useUniversalFilterState, FilterConfig } from "@/components/ui/UniversalFilters";
|
||||||
import { fmtBytes, getAppFavicon, formatAppLabel } from "@/lib/utils";
|
import { fmtBytes, getAppFavicon, formatAppLabel } from "@/lib/utils";
|
||||||
import { BarChart, Bar, XAxis, YAxis, CartesianGrid, Tooltip as RechartsTooltip, ResponsiveContainer } from "recharts";
|
import { BarChart, Bar, XAxis, YAxis, CartesianGrid, Tooltip as RechartsTooltip, ResponsiveContainer } from "recharts";
|
||||||
|
import { HelpTrigger } from "@/components/help/HelpTrigger";
|
||||||
|
|
||||||
export default function AppsPage() {
|
export default function AppsPage() {
|
||||||
const [mounted, setMounted] = useState(false);
|
const [mounted, setMounted] = useState(false);
|
||||||
@@ -130,7 +131,7 @@ export default function AppsPage() {
|
|||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<div>
|
<div>
|
||||||
<h2 className="text-2xl font-bold tracking-tight text-white flex items-center gap-3">
|
<h2 className="text-3xl font-bold tracking-tight text-white flex items-center gap-3">
|
||||||
Applications
|
Applications
|
||||||
{isLoading && <Loader2 className="w-5 h-5 animate-spin text-muted-foreground" />}
|
{isLoading && <Loader2 className="w-5 h-5 animate-spin text-muted-foreground" />}
|
||||||
</h2>
|
</h2>
|
||||||
@@ -138,6 +139,7 @@ export default function AppsPage() {
|
|||||||
Click any application to see which agents and devices are using it.
|
Click any application to see which agents and devices are using it.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
<HelpTrigger pageId="apps" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<UniversalFilters
|
<UniversalFilters
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import { fmtBytes } from "@/lib/utils";
|
|||||||
import { Loader2, ChevronRight, ChevronDown } from "lucide-react";
|
import { Loader2, ChevronRight, ChevronDown } from "lucide-react";
|
||||||
|
|
||||||
import { UniversalFilters, useUniversalFilterState, FilterConfig } from "@/components/ui/UniversalFilters";
|
import { UniversalFilters, useUniversalFilterState, FilterConfig } from "@/components/ui/UniversalFilters";
|
||||||
|
import { HelpTrigger } from "@/components/help/HelpTrigger";
|
||||||
|
|
||||||
export default function DevicesPage() {
|
export default function DevicesPage() {
|
||||||
const [mounted, setMounted] = useState(false);
|
const [mounted, setMounted] = useState(false);
|
||||||
@@ -171,6 +172,7 @@ export default function DevicesPage() {
|
|||||||
<span className="text-slate-500 text-xs">Click an IP to see app usage & flows.</span>
|
<span className="text-slate-500 text-xs">Click an IP to see app usage & flows.</span>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
<HelpTrigger pageId="devices" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<UniversalFilters
|
<UniversalFilters
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import { DataTable, Column } from "@/components/ui/DataTable";
|
|||||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/Card";
|
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/Card";
|
||||||
import { Loader2 } from "lucide-react";
|
import { Loader2 } from "lucide-react";
|
||||||
import { UniversalFilters, useUniversalFilterState, FilterConfig } from "@/components/ui/UniversalFilters";
|
import { UniversalFilters, useUniversalFilterState, FilterConfig } from "@/components/ui/UniversalFilters";
|
||||||
|
import { HelpTrigger } from "@/components/help/HelpTrigger";
|
||||||
|
|
||||||
export default function DNSPage() {
|
export default function DNSPage() {
|
||||||
const [mounted, setMounted] = useState(false);
|
const [mounted, setMounted] = useState(false);
|
||||||
@@ -95,11 +96,15 @@ export default function DNSPage() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<h2 className="text-2xl font-bold tracking-tight text-white flex items-center gap-3">
|
<div>
|
||||||
|
<h2 className="text-3xl font-bold tracking-tight text-white flex items-center gap-3">
|
||||||
DNS Intelligence
|
DNS Intelligence
|
||||||
{isLoading && <Loader2 className="w-5 h-5 animate-spin text-muted-foreground" />}
|
{isLoading && <Loader2 className="w-5 h-5 animate-spin text-muted-foreground" />}
|
||||||
</h2>
|
</h2>
|
||||||
|
<p className="text-muted-foreground mt-1">Domain Name System resolution analytics and query logs.</p>
|
||||||
|
</div>
|
||||||
|
<HelpTrigger pageId="dns" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<UniversalFilters
|
<UniversalFilters
|
||||||
|
|||||||
@@ -24,6 +24,8 @@ function ClickHint() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
import { HelpTrigger } from "@/components/help/HelpTrigger";
|
||||||
|
|
||||||
export default function DpiAnalyticsPage() {
|
export default function DpiAnalyticsPage() {
|
||||||
const { timeRange } = useTimeFilter();
|
const { timeRange } = useTimeFilter();
|
||||||
// Detail panel state
|
// Detail panel state
|
||||||
@@ -59,13 +61,16 @@ export default function DpiAnalyticsPage() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
<div>
|
<div>
|
||||||
<h1 className="text-3xl font-bold text-white tracking-tight">BackOne Metadata</h1>
|
<h1 className="text-3xl font-bold text-white tracking-tight">DPI MetaData</h1>
|
||||||
<p className="text-slate-400 mt-2">
|
<p className="text-slate-400 mt-2">
|
||||||
Deep Packet Inspection metadata and fingerprinting.{" "}
|
Deep Packet Inspection metadata and fingerprinting.{" "}
|
||||||
<span className="text-blue-400 text-sm">Click any row to view device-level detail.</span>
|
<span className="text-blue-400 text-sm">Click any row to view device-level detail.</span>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
<HelpTrigger pageId="dpi-analytics" />
|
||||||
|
</div>
|
||||||
|
|
||||||
<Tabs>
|
<Tabs>
|
||||||
{/* ── Hostnames & Servers ──────────────────────────────────────────── */}
|
{/* ── Hostnames & Servers ──────────────────────────────────────────── */}
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import { UniversalFilters, useUniversalFilterState, FilterConfig } from "@/compo
|
|||||||
|
|
||||||
import { useSearchParams } from "next/navigation";
|
import { useSearchParams } from "next/navigation";
|
||||||
import { Suspense } from "react";
|
import { Suspense } from "react";
|
||||||
|
import { HelpTrigger } from "@/components/help/HelpTrigger";
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -158,10 +159,14 @@ function EventsContent() {
|
|||||||
return (
|
return (
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<h2 className="text-2xl font-bold tracking-tight text-white flex items-center gap-3">
|
<div>
|
||||||
|
<h2 className="text-3xl font-bold tracking-tight text-white flex items-center gap-3">
|
||||||
Network Events
|
Network Events
|
||||||
{isLoading && <Loader2 className="w-5 h-5 animate-spin text-muted-foreground" />}
|
{isLoading && <Loader2 className="w-5 h-5 animate-spin text-muted-foreground" />}
|
||||||
</h2>
|
</h2>
|
||||||
|
<p className="text-muted-foreground mt-1">Real-time system, security, and interface network log events.</p>
|
||||||
|
</div>
|
||||||
|
<HelpTrigger pageId="events" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<UniversalFilters
|
<UniversalFilters
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import { fmtBytes, formatAppLabel } from "@/lib/utils";
|
|||||||
import { IpDetails } from "@/components/ui/IpDetails";
|
import { IpDetails } from "@/components/ui/IpDetails";
|
||||||
import { DeviceDetailModal } from "@/components/ui/DeviceDetailModal";
|
import { DeviceDetailModal } from "@/components/ui/DeviceDetailModal";
|
||||||
import { UniversalFilters, useUniversalFilterState, FilterConfig } from "@/components/ui/UniversalFilters";
|
import { UniversalFilters, useUniversalFilterState, FilterConfig } from "@/components/ui/UniversalFilters";
|
||||||
|
import { HelpTrigger } from "@/components/help/HelpTrigger";
|
||||||
|
|
||||||
function explainAppOrPort(appLabel: string | null, domain: string | null, port: number) {
|
function explainAppOrPort(appLabel: string | null, domain: string | null, port: number) {
|
||||||
const label = appLabel || domain || "";
|
const label = appLabel || domain || "";
|
||||||
@@ -208,10 +209,14 @@ export default function FlowsPage() {
|
|||||||
return (
|
return (
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<h2 className="text-2xl font-bold tracking-tight text-white flex items-center gap-3">
|
<div>
|
||||||
|
<h2 className="text-3xl font-bold tracking-tight text-white flex items-center gap-3">
|
||||||
Active Flows
|
Active Flows
|
||||||
{isLoading && <Loader2 className="w-5 h-5 animate-spin text-muted-foreground" />}
|
{isLoading && <Loader2 className="w-5 h-5 animate-spin text-muted-foreground" />}
|
||||||
</h2>
|
</h2>
|
||||||
|
<p className="text-muted-foreground mt-1">Real-time IP connection flows and protocol analysis.</p>
|
||||||
|
</div>
|
||||||
|
<HelpTrigger pageId="flows" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<UniversalFilters
|
<UniversalFilters
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import { Loader2 } from "lucide-react";
|
|||||||
import { fmtBytes } from "@/lib/utils";
|
import { fmtBytes } from "@/lib/utils";
|
||||||
import { WorldMap } from "@/components/ui/WorldMap";
|
import { WorldMap } from "@/components/ui/WorldMap";
|
||||||
import { UniversalFilters, useUniversalFilterState, FilterConfig } from "@/components/ui/UniversalFilters";
|
import { UniversalFilters, useUniversalFilterState, FilterConfig } from "@/components/ui/UniversalFilters";
|
||||||
|
import { HelpTrigger } from "@/components/help/HelpTrigger";
|
||||||
|
|
||||||
export default function GeographyPage() {
|
export default function GeographyPage() {
|
||||||
const [mounted, setMounted] = useState(false);
|
const [mounted, setMounted] = useState(false);
|
||||||
@@ -102,14 +103,15 @@ export default function GeographyPage() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-8 animate-fade-in pb-10">
|
<div className="space-y-8 animate-fade-in pb-10">
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<div>
|
<div>
|
||||||
<h2 className="text-3xl font-bold tracking-tight text-white flex items-center gap-3">
|
<h2 className="text-3xl font-bold tracking-tight text-white flex items-center gap-3">
|
||||||
Geographic Traffic
|
Geo Traffic
|
||||||
{isLoading && <Loader2 className="w-5 h-5 animate-spin text-primary" />}
|
{isLoading && <Loader2 className="w-5 h-5 animate-spin text-primary" />}
|
||||||
</h2>
|
</h2>
|
||||||
<p className="text-muted-foreground mt-1">Visualize data flow and activity across the globe.</p>
|
<p className="text-muted-foreground mt-1">Geographic distribution of inbound and outbound traffic by country.</p>
|
||||||
</div>
|
</div>
|
||||||
|
<HelpTrigger pageId="geography" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<UniversalFilters
|
<UniversalFilters
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import { Loader2, Bitcoin, Shield, Server, Lock, AlertTriangle, Key, Globe, Netw
|
|||||||
import { DeviceDetailModal } from "@/components/ui/DeviceDetailModal";
|
import { DeviceDetailModal } from "@/components/ui/DeviceDetailModal";
|
||||||
import { TimestampCell } from "@/components/ui/TimestampCell";
|
import { TimestampCell } from "@/components/ui/TimestampCell";
|
||||||
import { fmtBytes } from "@/lib/utils";
|
import { fmtBytes } from "@/lib/utils";
|
||||||
|
import { HelpTrigger } from "@/components/help/HelpTrigger";
|
||||||
|
|
||||||
export default function IntelligencePage() {
|
export default function IntelligencePage() {
|
||||||
const [mounted, setMounted] = useState(false);
|
const [mounted, setMounted] = useState(false);
|
||||||
@@ -370,11 +371,15 @@ export default function IntelligencePage() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<h2 className="text-2xl font-bold tracking-tight text-white flex items-center gap-3">
|
<div>
|
||||||
Threat Intelligence Feeds
|
<h2 className="text-3xl font-bold tracking-tight text-white flex items-center gap-3">
|
||||||
|
Threat Intelligence
|
||||||
{loadingStats && <Loader2 className="w-5 h-5 animate-spin text-muted-foreground" />}
|
{loadingStats && <Loader2 className="w-5 h-5 animate-spin text-muted-foreground" />}
|
||||||
</h2>
|
</h2>
|
||||||
|
<p className="text-muted-foreground mt-1">Deep threat intelligence feeds and security detections.</p>
|
||||||
|
</div>
|
||||||
|
<HelpTrigger pageId="intelligence" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,8 @@ import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/Card";
|
|||||||
import { DropdownFilterSelect } from "@/components/ui/DropdownFilterSelect";
|
import { DropdownFilterSelect } from "@/components/ui/DropdownFilterSelect";
|
||||||
import { Loader2, Search, AppWindow, HelpCircle, ChevronLeft, ChevronRight, ChevronsLeft, ChevronsRight, X, Info, Tag, Box, Shield, Server } from "lucide-react";
|
import { Loader2, Search, AppWindow, HelpCircle, ChevronLeft, ChevronRight, ChevronsLeft, ChevronsRight, X, Info, Tag, Box, Shield, Server } from "lucide-react";
|
||||||
|
|
||||||
|
import { HelpTrigger } from "@/components/help/HelpTrigger";
|
||||||
|
|
||||||
export default function LookupPage() {
|
export default function LookupPage() {
|
||||||
const [mounted, setMounted] = useState(false);
|
const [mounted, setMounted] = useState(false);
|
||||||
const [searchQuery, setSearchQuery] = useState("");
|
const [searchQuery, setSearchQuery] = useState("");
|
||||||
@@ -54,15 +56,18 @@ export default function LookupPage() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-6 animate-fade-in pb-10">
|
<div className="space-y-6 animate-fade-in pb-10">
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
<div>
|
<div>
|
||||||
<h1 className="text-3xl font-bold text-white tracking-tight flex items-center gap-3">
|
<h1 className="text-3xl font-bold text-white tracking-tight flex items-center gap-3">
|
||||||
Application Database
|
App Lookup
|
||||||
{isLoading && <Loader2 className="w-5 h-5 animate-spin text-primary" />}
|
{isLoading && <Loader2 className="w-5 h-5 animate-spin text-primary" />}
|
||||||
</h1>
|
</h1>
|
||||||
<p className="text-slate-400 mt-2">
|
<p className="text-slate-400 mt-2">
|
||||||
Search BackOne's extensive classification database of over 2,500+ apps, protocols, and services.
|
Search BackOne's extensive classification database of over 2,500+ apps, protocols, and services.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
<HelpTrigger pageId="lookup" />
|
||||||
|
</div>
|
||||||
|
|
||||||
{/* Search & Filter Bar */}
|
{/* Search & Filter Bar */}
|
||||||
<div className="flex flex-col sm:flex-row gap-4 animate-slide-up relative z-50" style={{ animationDelay: '50ms' }}>
|
<div className="flex flex-col sm:flex-row gap-4 animate-slide-up relative z-50" style={{ animationDelay: '50ms' }}>
|
||||||
|
|||||||
@@ -12,6 +12,8 @@ import {
|
|||||||
import { IpDetails } from "@/components/ui/IpDetails";
|
import { IpDetails } from "@/components/ui/IpDetails";
|
||||||
import { RemoteIpDetailModal } from "@/components/ui/RemoteIpDetailModal";
|
import { RemoteIpDetailModal } from "@/components/ui/RemoteIpDetailModal";
|
||||||
|
|
||||||
|
import { HelpTrigger } from "@/components/help/HelpTrigger";
|
||||||
|
|
||||||
export default function NetworkInfrastructurePage() {
|
export default function NetworkInfrastructurePage() {
|
||||||
const [selectedRemoteIp, setSelectedRemoteIp] = useState<string | null>(null);
|
const [selectedRemoteIp, setSelectedRemoteIp] = useState<string | null>(null);
|
||||||
|
|
||||||
@@ -42,10 +44,13 @@ export default function NetworkInfrastructurePage() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
<div>
|
<div>
|
||||||
<h1 className="text-3xl font-bold text-white tracking-tight">Network Infrastructure</h1>
|
<h1 className="text-3xl font-bold text-white tracking-tight">Network Topology</h1>
|
||||||
<p className="text-slate-400 mt-2">Physical, logical, and geographic network topologies.</p>
|
<p className="text-slate-400 mt-2">Physical, logical, and geographic network topologies.</p>
|
||||||
</div>
|
</div>
|
||||||
|
<HelpTrigger pageId="network-infrastructure" />
|
||||||
|
</div>
|
||||||
|
|
||||||
<Tabs>
|
<Tabs>
|
||||||
<Tab label="Routing & Geography">
|
<Tab label="Routing & Geography">
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import { useAppCategories, useContinents } from "@/lib/api-with-context";
|
|||||||
import { fmtBytes } from "@/lib/utils";
|
import { fmtBytes } from "@/lib/utils";
|
||||||
import { UniversalFilters, useUniversalFilterState, FilterConfig } from "@/components/ui/UniversalFilters";
|
import { UniversalFilters, useUniversalFilterState, FilterConfig } from "@/components/ui/UniversalFilters";
|
||||||
import { CategoryDetailPanel } from "@/components/network/CategoryDetailPanel";
|
import { CategoryDetailPanel } from "@/components/network/CategoryDetailPanel";
|
||||||
|
import { HelpTrigger } from "@/components/help/HelpTrigger";
|
||||||
|
|
||||||
const COLORS = ['#58a6ff', '#3fb950', '#f0883e', '#bc8cff', '#f85149'];
|
const COLORS = ['#58a6ff', '#3fb950', '#f0883e', '#bc8cff', '#f85149'];
|
||||||
|
|
||||||
@@ -90,6 +91,7 @@ export default function NetworkIntelligencePage() {
|
|||||||
</h2>
|
</h2>
|
||||||
<p className="text-muted-foreground mt-1">Deep packet inspection analytics and geography.</p>
|
<p className="text-muted-foreground mt-1">Deep packet inspection analytics and geography.</p>
|
||||||
</div>
|
</div>
|
||||||
|
<HelpTrigger pageId="network-intelligence" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<UniversalFilters
|
<UniversalFilters
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import TlsVersionsChart from "@/components/security-audit/TlsVersionsChart";
|
|||||||
import TlsTables from "@/components/security-audit/TlsTables";
|
import TlsTables from "@/components/security-audit/TlsTables";
|
||||||
import SslSanTable from "@/components/security-audit/SslSanTable";
|
import SslSanTable from "@/components/security-audit/SslSanTable";
|
||||||
import { UniversalFilters, useUniversalFilterState, FilterConfig } from "@/components/ui/UniversalFilters";
|
import { UniversalFilters, useUniversalFilterState, FilterConfig } from "@/components/ui/UniversalFilters";
|
||||||
|
import { HelpTrigger } from "@/components/help/HelpTrigger";
|
||||||
|
|
||||||
export default function SecurityAuditPage() {
|
export default function SecurityAuditPage() {
|
||||||
const [mounted, setMounted] = useState(false);
|
const [mounted, setMounted] = useState(false);
|
||||||
@@ -53,12 +54,15 @@ export default function SecurityAuditPage() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
<div>
|
<div>
|
||||||
<h2 className="text-2xl font-bold text-white tracking-tight">Security & Encryption Audit</h2>
|
<h2 className="text-3xl font-bold text-white tracking-tight">Encryption Audit</h2>
|
||||||
<p className="text-sm text-slate-400 mt-1">
|
<p className="text-sm text-slate-400 mt-1">
|
||||||
Monitor TLS versions distribution, cipher suites, active certificate properties, and device risk profiles.
|
Monitor TLS versions distribution, cipher suites, active certificate properties, and device risk profiles.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
<HelpTrigger pageId="security-audit" />
|
||||||
|
</div>
|
||||||
|
|
||||||
<UniversalFilters
|
<UniversalFilters
|
||||||
filters={filterConfigs}
|
filters={filterConfigs}
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import { ThreatFilters } from "@/components/threats/ThreatFilters";
|
|||||||
import { ThreatRecommendationsDrawer } from "@/components/threats/ThreatRecommendationsDrawer";
|
import { ThreatRecommendationsDrawer } from "@/components/threats/ThreatRecommendationsDrawer";
|
||||||
import { useSearchParams } from "next/navigation";
|
import { useSearchParams } from "next/navigation";
|
||||||
import { formatAppLabel } from "@/lib/utils";
|
import { formatAppLabel } from "@/lib/utils";
|
||||||
|
import { HelpTrigger } from "@/components/help/HelpTrigger";
|
||||||
|
|
||||||
// ─── ThreatsContent ───────────────────────────────────────────────────────────
|
// ─── ThreatsContent ───────────────────────────────────────────────────────────
|
||||||
function ThreatsContent() {
|
function ThreatsContent() {
|
||||||
@@ -215,15 +216,18 @@ function ThreatsContent() {
|
|||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<div>
|
<div>
|
||||||
<h2 className="text-3xl font-bold tracking-tight text-white flex items-center gap-3">
|
<h2 className="text-3xl font-bold tracking-tight text-white flex items-center gap-3">
|
||||||
Threat Intelligence
|
Detected Threats
|
||||||
{threats.isLoading && <Loader2 className="w-5 h-5 animate-spin text-primary" />}
|
{threats.isLoading && <Loader2 className="w-5 h-5 animate-spin text-primary" />}
|
||||||
</h2>
|
</h2>
|
||||||
<p className="text-muted-foreground mt-1">Monitor and respond to network security threats.</p>
|
<p className="text-muted-foreground mt-1">Monitor and respond to network security threats.</p>
|
||||||
</div>
|
</div>
|
||||||
|
<div className="flex items-center gap-3">
|
||||||
<div className="flex items-center gap-2 text-sm font-medium text-emerald-400 bg-emerald-500/10 border border-emerald-500/20 px-4 py-2 rounded-lg shadow-[0_0_15px_rgba(16,185,129,0.1)]">
|
<div className="flex items-center gap-2 text-sm font-medium text-emerald-400 bg-emerald-500/10 border border-emerald-500/20 px-4 py-2 rounded-lg shadow-[0_0_15px_rgba(16,185,129,0.1)]">
|
||||||
<ShieldCheck className="h-4 w-4" />
|
<ShieldCheck className="h-4 w-4" />
|
||||||
<span>Active Monitoring</span>
|
<span>Active Monitoring</span>
|
||||||
</div>
|
</div>
|
||||||
|
<HelpTrigger pageId="threats" />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Filters above the table */}
|
{/* Filters above the table */}
|
||||||
|
|||||||
Reference in new issue
Block a user