Sync cezen-portal/ from live working copy (V1.0 GA polish + search-icon ordering fix)

Brings the ISO-packaged portal copy current with the live cezen-portal/
working directory, which had drifted across the entire V1.0 GA polish
initiative (Phases 1-10) plus this session's topnav icon-order bugfix.

- 52 existing files updated, 2 new pages added (about.html, health.html)
- cgit/cezen-portal/ is now byte-identical to cezen-portal/
- Backend files (rag_ingest.py, cezen_license.py) already in sync;
  main.py intentionally left as-is (cgit is ahead there with
  workstation-category commercial-fit logic not yet backported to the
  live working copy)

Next: rebuild the ISO (bash autoinstall/build-iso.sh) to bake this in.
This commit is contained in:
Jino Jose committed 2026-07-09 11:11:38 +05:30
1 parent 7540da04a8
commit b3eec34158
57 files changed
+2832 -1354

No files matched your search

+69 -98
View File
@@ -4,7 +4,7 @@
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>MCP & Enterprise Connectors — Nexus One AI</title>
<link rel="stylesheet" href="style.css?v=4">
<link rel="stylesheet" href="style.css?v=12">
<style>
.cn-content { max-width:1060px; margin:0 auto; padding:32px 40px 60px; }
@media(max-width:768px){ .cn-content { padding:20px 16px 48px; } }
@@ -35,7 +35,8 @@
.cn-tile:hover:not(.connected):not(.syncing) { border-color:var(--purple); }
.cn-tile-top { display:flex; align-items:flex-start; gap:12px; margin-bottom:12px; }
.cn-tile-icon { font-size:28px; flex-shrink:0; }
.cn-tile-icon { width:28px; height:28px; color:var(--text-secondary); flex-shrink:0; }
.cn-tile-icon svg { width:100%; height:100%; }
.cn-tile-info { flex:1; min-width:0; }
.cn-tile-name { font-size:14px; font-weight:700; color:var(--ink); }
.cn-tile-type { font-size:11px; color:var(--lt); margin-top:2px; }
@@ -105,7 +106,8 @@
/* ── Empty state ── */
.cn-empty { text-align:center; padding:40px 20px; color:var(--lt); font-size:13px; }
.cn-empty-icon { font-size:36px; margin-bottom:10px; }
.cn-empty-icon { width:36px; height:36px; margin:0 auto 10px; color:var(--text-secondary); opacity:.45; }
.cn-empty-icon svg { width:100%; height:100%; }
/* ── Toast ── */
.cn-toast { position:fixed; bottom:28px; right:28px; background:var(--ink); color:var(--navy2); padding:11px 20px; border-radius:10px; font-size:13px; font-weight:600; z-index:999; opacity:0; transform:translateY(8px); transition:.2s; pointer-events:none; }
@@ -146,6 +148,7 @@
<a href="dashboard.html">Dashboard</a>
<a href="analytics.html">Usage Analytics</a>
<a href="audit.html">Governance & Audit</a>
<a href="health.html">Health Center</a>
<a href="feedback.html">Feedback &amp; Ratings</a>
<span class="nav-drop-cat">MANAGE /</span>
<a href="users.html">Users</a>
@@ -185,7 +188,11 @@
</div>
</div>
</nav>
<a href="notifications.html" style="position:relative">🔔</a>
<a href="notifications.html" class="nav-icon-btn" style="position:relative" title="Notifications">
<span class="icon-svg icon-md" aria-hidden="true">
<svg viewBox="0 0 24 24"><path d="M18 8A6 6 0 0 0 6 8c0 7-3 9-3 9h18s-3-2-3-9"/><path d="M13.73 21a2 2 0 0 1-3.46 0"/></svg>
</span>
</a>
<span class="badge" data-brand="tier">Basic Tier</span>
<div id="nav-org-logo" class="nav-org-logo"></div>
</header>
@@ -198,6 +205,8 @@
<div class="cn-content">
<div class="an-notice warn" id="cn-notice" style="display:none;background:var(--amber-lt);border:1px solid var(--amber-border);border-radius:10px;padding:12px 16px;font-size:13px;color:var(--amber);margin-bottom:20px"></div>
<!-- Stats -->
<div class="cn-stats">
<div class="cn-stat">
@@ -225,13 +234,13 @@
<!-- Folder connectors -->
<div class="cn-card">
<div class="cn-card-head">
<span style="font-size:20px">📁</span>
<span class="icon-badge" aria-hidden="true"><span class="icon-svg icon-xl"><svg viewBox="0 0 24 24"><path d="M3 7h6l2 2h10v8a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"/><path d="M3 7V5a2 2 0 0 1 2-2h4l2 2h8a2 2 0 0 1 2 2v2"/></svg></span></span>
<div class="cn-card-title">Local &amp; Network Folder Connectors</div>
<button class="cn-btn primary" onclick="openModal('folder','new')">+ Add Folder</button>
<button class="cn-btn primary" onclick="openModal('folder','new')"><span class="icon-label"><span class="icon-svg icon-sm" aria-hidden="true"><svg viewBox="0 0 24 24"><path d="M12 5v14M5 12h14"/></svg></span>Add Folder</span></button>
</div>
<div class="cn-card-body">
<div class="cn-grid" id="folder-grid">
<div class="cn-empty"><div class="cn-empty-icon">📂</div>Loading…</div>
<div class="cn-empty"><div class="cn-empty-icon icon-svg"><svg viewBox="0 0 24 24"><path d="M3 7h6l2 2h10v8a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"/><path d="M3 7V5a2 2 0 0 1 2-2h4l2 2h8a2 2 0 0 1 2 2v2"/></svg></div>Loading…</div>
</div>
</div>
</div>
@@ -239,13 +248,13 @@
<!-- Database connectors -->
<div class="cn-card">
<div class="cn-card-head">
<span style="font-size:20px">🗄️</span>
<span class="icon-badge" aria-hidden="true"><span class="icon-svg icon-xl"><svg viewBox="0 0 24 24"><ellipse cx="12" cy="5" rx="8" ry="3"/><path d="M4 5v14c0 1.66 3.58 3 8 3s8-1.34 8-3V5"/><path d="M4 12c0 1.66 3.58 3 8 3s8-1.34 8-3"/></svg></span></span>
<div class="cn-card-title">Read-Only Database Connectors</div>
<button class="cn-btn primary" onclick="openModal('db','new')">+ Add Database</button>
<button class="cn-btn primary" onclick="openModal('db','new')"><span class="icon-label"><span class="icon-svg icon-sm" aria-hidden="true"><svg viewBox="0 0 24 24"><path d="M12 5v14M5 12h14"/></svg></span>Add Database</span></button>
</div>
<div class="cn-card-body">
<div class="cn-grid" id="db-grid">
<div class="cn-empty"><div class="cn-empty-icon">🗄️</div>Loading…</div>
<div class="cn-empty"><div class="cn-empty-icon icon-svg"><svg viewBox="0 0 24 24"><ellipse cx="12" cy="5" rx="8" ry="3"/><path d="M4 5v14c0 1.66 3.58 3 8 3s8-1.34 8-3V5"/><path d="M4 12c0 1.66 3.58 3 8 3s8-1.34 8-3"/></svg></div>Loading…</div>
</div>
</div>
</div>
@@ -253,9 +262,9 @@
<!-- Sync log -->
<div class="cn-card">
<div class="cn-card-head">
<span style="font-size:20px">📋</span>
<span class="icon-badge" aria-hidden="true"><span class="icon-svg icon-xl"><svg viewBox="0 0 24 24"><path d="M9 3h6"/><path d="M10 7h4"/><rect x="5" y="5" width="14" height="16" rx="2"/></svg></span></span>
<div class="cn-card-title">Sync Activity Log</div>
<button class="cn-btn" onclick="loadLog()">↻ Refresh</button>
<button class="cn-btn" onclick="loadLog()"><span class="icon-label"><span class="icon-svg icon-sm" aria-hidden="true"><svg viewBox="0 0 24 24"><path d="M21 12a9 9 0 1 1-2.64-6.36"/><path d="M21 3v6h-6"/></svg></span>Refresh</span></button>
</div>
<div class="cn-card-body">
<div class="cn-log" id="sync-log">
@@ -270,14 +279,14 @@
<div class="cn-modal-overlay" id="modal-overlay" onclick="closeModalIf(event)">
<div class="cn-modal">
<div class="cn-modal-head">
<span id="modal-icon" style="font-size:22px">📁</span>
<span id="modal-icon" class="icon-svg icon-xl" aria-hidden="true"><svg viewBox="0 0 24 24"><path d="M3 7h6l2 2h10v8a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"/><path d="M3 7V5a2 2 0 0 1 2-2h4l2 2h8a2 2 0 0 1 2 2v2"/></svg></span>
<div class="cn-modal-title" id="modal-title">Configure Connector</div>
<button class="cn-modal-close" onclick="closeModal()">✕</button>
<button class="cn-modal-close" onclick="closeModal()"><span class="icon-svg icon-sm" aria-hidden="true"><svg viewBox="0 0 24 24"><path d="M6 6l12 12M18 6 6 18"/></svg></span></button>
</div>
<div class="cn-modal-body" id="modal-body"></div>
<div class="cn-modal-foot">
<button class="cn-btn" onclick="testConnection()">🔌 Test Connection</button>
<button class="cn-btn primary" onclick="saveConnector()">Save Connector</button>
<button class="cn-btn" onclick="testConnection()"><span class="icon-label"><span class="icon-svg icon-sm" aria-hidden="true"><svg viewBox="0 0 24 24"><path d="M8 7V3"/><path d="M16 7V3"/><path d="M9 12h6"/><path d="M9 16h6"/><rect x="6" y="7" width="12" height="13" rx="2"/></svg></span>Test Connection</span></button>
<button class="cn-btn primary" onclick="saveConnector()"><span class="icon-label"><span class="icon-svg icon-sm" aria-hidden="true"><svg viewBox="0 0 24 24"><path d="M5 21h14"/><path d="M19 21V7l-3-3H5v17"/><path d="M9 10h6"/><path d="M9 14h6"/></svg></span>Save Connector</span></button>
</div>
</div>
</div>
@@ -287,53 +296,38 @@
<script>
const _API = '/api';
const CN_KEY = 'cezen_connectors';
const ICONS = {
folder: '<span class="icon-svg icon-2xl" aria-hidden="true"><svg viewBox="0 0 24 24"><path d="M3 7h6l2 2h10v8a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"/><path d="M3 7V5a2 2 0 0 1 2-2h4l2 2h8a2 2 0 0 1 2 2v2"/></svg></span>',
folderSmall: '<span class="icon-svg icon-xl" aria-hidden="true"><svg viewBox="0 0 24 24"><path d="M3 7h6l2 2h10v8a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"/><path d="M3 7V5a2 2 0 0 1 2-2h4l2 2h8a2 2 0 0 1 2 2v2"/></svg></span>',
database: '<span class="icon-svg icon-2xl" aria-hidden="true"><svg viewBox="0 0 24 24"><ellipse cx="12" cy="5" rx="8" ry="3"/><path d="M4 5v14c0 1.66 3.58 3 8 3s8-1.34 8-3V5"/><path d="M4 12c0 1.66 3.58 3 8 3s8-1.34 8-3"/></svg></span>',
databaseSmall: '<span class="icon-svg icon-xl" aria-hidden="true"><svg viewBox="0 0 24 24"><ellipse cx="12" cy="5" rx="8" ry="3"/><path d="M4 5v14c0 1.66 3.58 3 8 3s8-1.34 8-3V5"/><path d="M4 12c0 1.66 3.58 3 8 3s8-1.34 8-3"/></svg></span>',
chart: '<span class="icon-svg icon-2xl" aria-hidden="true"><svg viewBox="0 0 24 24"><path d="M4 19h16"/><path d="M7 16V8"/><path d="M12 16V5"/><path d="M17 16v-3"/></svg></span>',
shield: '<span class="icon-svg icon-2xl" aria-hidden="true"><svg viewBox="0 0 24 24"><path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"/></svg></span>',
refresh: '<span class="icon-svg icon-sm" aria-hidden="true"><svg viewBox="0 0 24 24"><path d="M21 12a9 9 0 1 1-2.64-6.36"/><path d="M21 3v6h-6"/></svg></span>',
settings: '<span class="icon-svg icon-sm" aria-hidden="true"><svg viewBox="0 0 24 24"><circle cx="12" cy="12" r="3"/><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 1 1-2.83 2.83l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 1 1-4 0v-.09a1.65 1.65 0 0 0-1-1.51 1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 1 1-2.83-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 1 1 0-4h.09a1.65 1.65 0 0 0 1.51-1 1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 1 1 2.83-2.83l.06.06a1.65 1.65 0 0 0 1.82.33h.01A1.65 1.65 0 0 0 10.5 3.1V3a2 2 0 1 1 4 0v.09a1.65 1.65 0 0 0 1 1.51h.01a1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 1 1 2.83 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82v.01a1.65 1.65 0 0 0 1.51 1H21a2 2 0 1 1 0 4h-.09a1.65 1.65 0 0 0-1.51 1z"/></svg></span>',
test: '<span class="icon-svg icon-sm" aria-hidden="true"><svg viewBox="0 0 24 24"><path d="M8 7V3"/><path d="M16 7V3"/><path d="M9 12h6"/><path d="M9 16h6"/><rect x="6" y="7" width="12" height="13" rx="2"/></svg></span>',
alert: '<span class="icon-svg icon-sm" aria-hidden="true"><svg viewBox="0 0 24 24"><path d="M12 9v4"/><path d="M12 17h.01"/><path d="M10.29 3.86 1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z"/></svg></span>'
};
let connectors = [];
let editingId = null;
let modalType = null;
// ── Mock data ─────────────────────────────────────────────────────────────────
const MOCK_CONNECTORS = [
{
id:'cn_1', type:'folder', name:'HR Documents Share',
icon:'📁', status:'ok',
config:{ path:'\\\\fileserver\\hr-docs', watchInterval:30, targetCollection:'HR Policies', includeExts:'pdf,docx,xlsx', recursive:true },
stats:{ files:142, lastSync: new Date(Date.now()-25*60000).toISOString(), errors:0 }
},
{
id:'cn_2', type:'folder', name:'Finance Reports',
icon:'📊', status:'ok',
config:{ path:'/mnt/nas/finance', watchInterval:60, targetCollection:'Finance Reports', includeExts:'xlsx,pdf,csv', recursive:false },
stats:{ files:67, lastSync: new Date(Date.now()-2*3600000).toISOString(), errors:0 }
},
{
id:'cn_3', type:'folder', name:'Legal Contracts',
icon:'⚖️', status:'error',
config:{ path:'\\\\lawserver\\contracts', watchInterval:15, targetCollection:'Legal Contracts', includeExts:'pdf,docx', recursive:true },
stats:{ files:89, lastSync: new Date(Date.now()-48*3600000).toISOString(), errors:3, lastError:'Mount point not reachable: \\\\lawserver\\contracts' }
},
{
id:'cn_db1', type:'db', name:'ERP Database (Read-only)',
icon:'🗄️', status:'ok',
config:{ driver:'postgresql', host:'erp-db.internal', port:'5432', database:'erp_prod', username:'cezen_ro', schema:'public', tables:'employees,departments,leave_requests' },
stats:{ rowsRead:12400, lastSync: new Date(Date.now()-10*60000).toISOString(), errors:0 }
},
{
id:'cn_db2', type:'db', name:'HRMS MySQL',
icon:'🗃️', status:'idle',
config:{ driver:'mysql', host:'hrms.internal', port:'3306', database:'hrms', username:'cezen_ro', schema:'', tables:'employees,payroll,attendance' },
stats:{ rowsRead:0, lastSync:null, errors:0 }
}
];
// ── Init ──────────────────────────────────────────────────────────────────────
async function init() {
const notice = document.getElementById('cn-notice');
try {
const r = await fetch(`${_API}/connectors`, { credentials:'include' });
if (!r.ok) throw new Error();
connectors = await r.json();
notice.style.display = 'none';
} catch(e) {
connectors = JSON.parse(localStorage.getItem(CN_KEY) || 'null') || MOCK_CONNECTORS;
const cached = JSON.parse(localStorage.getItem(CN_KEY) || 'null');
connectors = cached || [];
notice.textContent = cached
? "Couldn't reach the API — showing the last known connector state from this browser. It may be out of date."
: "Couldn't reach the API to load connectors. This is not showing example or cached data — check API connectivity and reload.";
notice.style.display = '';
}
renderAll();
loadLog();
@@ -362,7 +356,7 @@ function renderFolders() {
const grid = document.getElementById('folder-grid');
const items = connectors.filter(c => c.type === 'folder');
if (!items.length) {
grid.innerHTML = `<div class="cn-empty"><div class="cn-empty-icon">📂</div>No folder connectors yet — click <strong>Add Folder</strong> to get started.</div>`;
grid.innerHTML = `<div class="cn-empty"><div class="cn-empty-icon icon-svg"><svg viewBox="0 0 24 24"><path d="M3 7h6l2 2h10v8a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"/><path d="M3 7V5a2 2 0 0 1 2-2h4l2 2h8a2 2 0 0 1 2 2v2"/></svg></div>No folder connectors yet — click <strong>Add Folder</strong> to get started.</div>`;
return;
}
grid.innerHTML = items.map(c => folderTileHtml(c)).join('');
@@ -372,7 +366,7 @@ function renderDbs() {
const grid = document.getElementById('db-grid');
const items = connectors.filter(c => c.type === 'db');
if (!items.length) {
grid.innerHTML = `<div class="cn-empty"><div class="cn-empty-icon">🗄️</div>No database connectors yet — click <strong>Add Database</strong>.</div>`;
grid.innerHTML = `<div class="cn-empty"><div class="cn-empty-icon icon-svg"><svg viewBox="0 0 24 24"><ellipse cx="12" cy="5" rx="8" ry="3"/><path d="M4 5v14c0 1.66 3.58 3 8 3s8-1.34 8-3V5"/><path d="M4 12c0 1.66 3.58 3 8 3s8-1.34 8-3"/></svg></div>No database connectors yet — click <strong>Add Database</strong>.</div>`;
return;
}
grid.innerHTML = items.map(c => dbTileHtml(c)).join('');
@@ -384,7 +378,7 @@ function folderTileHtml(c) {
const lastSync = c.stats?.lastSync ? fmtAgo(new Date(c.stats.lastSync)) : 'Never';
return `<div class="cn-tile ${c.status}" id="cn-tile-${c.id}">
<div class="cn-tile-top">
<span class="cn-tile-icon">${c.icon||'📁'}</span>
<span class="cn-tile-icon">${c.icon||ICONS.folder}</span>
<div class="cn-tile-info">
<div class="cn-tile-name">${escHtml(c.name)}</div>
<div class="cn-tile-type">Local / SMB / NFS Folder</div>
@@ -395,12 +389,12 @@ function folderTileHtml(c) {
<strong>Path:</strong> ${escHtml(c.config?.path||'—')}<br>
<strong>Collection:</strong> ${escHtml(c.config?.targetCollection||'—')}<br>
<strong>Files:</strong> ${(c.stats?.files||0).toLocaleString()} indexed · Last sync: ${lastSync}
${c.stats?.lastError ? `<br><span style="color:#DC2626">⚠ ${escHtml(c.stats.lastError)}</span>` : ''}
${c.stats?.lastError ? `<br><span class="icon-label" style="color:#DC2626">${ICONS.alert}<span>${escHtml(c.stats.lastError)}</span></span>` : ''}
</div>
<div class="cn-sync-bar"><div class="cn-sync-fill" style="width:${c.status==='ok'?100:c.status==='error'?20:0}%"></div></div>
<div class="cn-tile-actions" style="margin-top:12px">
<button class="cn-btn" onclick="syncNow('${c.id}')">↻ Sync Now</button>
<button class="cn-btn" onclick="openModal('folder','${c.id}')">⚙ Configure</button>
<button class="cn-btn" onclick="syncNow('${c.id}')"><span class="icon-label">${ICONS.refresh}<span>Sync Now</span></span></button>
<button class="cn-btn" onclick="openModal('folder','${c.id}')"><span class="icon-label">${ICONS.settings}<span>Configure</span></span></button>
<button class="cn-btn danger" onclick="removeConnector('${c.id}')">Remove</button>
</div>
</div>`;
@@ -412,7 +406,7 @@ function dbTileHtml(c) {
const lastSync = c.stats?.lastSync ? fmtAgo(new Date(c.stats.lastSync)) : 'Never';
return `<div class="cn-tile ${c.status}" id="cn-tile-${c.id}">
<div class="cn-tile-top">
<span class="cn-tile-icon">${c.icon||'🗄️'}</span>
<span class="cn-tile-icon">${c.icon||ICONS.database}</span>
<div class="cn-tile-info">
<div class="cn-tile-name">${escHtml(c.name)}</div>
<div class="cn-tile-type">${escHtml((c.config?.driver||'database').toUpperCase())} · Read-only</div>
@@ -425,8 +419,8 @@ function dbTileHtml(c) {
<strong>Rows read:</strong> ${(c.stats?.rowsRead||0).toLocaleString()} · Last query: ${lastSync}
</div>
<div class="cn-tile-actions">
<button class="cn-btn" onclick="testDb('${c.id}')">🔌 Test</button>
<button class="cn-btn" onclick="openModal('db','${c.id}')">⚙ Configure</button>
<button class="cn-btn" onclick="testDb('${c.id}')"><span class="icon-label">${ICONS.test}<span>Test</span></span></button>
<button class="cn-btn" onclick="openModal('db','${c.id}')"><span class="icon-label">${ICONS.settings}<span>Configure</span></span></button>
<button class="cn-btn danger" onclick="removeConnector('${c.id}')">Remove</button>
</div>
</div>`;
@@ -439,7 +433,7 @@ function openModal(type, id) {
const existing = editingId ? connectors.find(c => c.id === editingId) : null;
const cfg = existing?.config || {};
document.getElementById('modal-icon').textContent = type === 'folder' ? '📁' : '🗄️';
document.getElementById('modal-icon').innerHTML = type === 'folder' ? ICONS.folderSmall : ICONS.databaseSmall;
document.getElementById('modal-title').textContent = existing
? `Configure — ${existing.name}`
: (type === 'folder' ? 'Add Folder Connector' : 'Add Database Connector');
@@ -556,7 +550,7 @@ function dbModalHtml(cfg, cn) {
// ── Save / remove ─────────────────────────────────────────────────────────────
function saveConnector() {
const name = document.getElementById('m-name')?.value?.trim();
if (!name) { toast('Please enter a name'); return; }
if (!name) { showToast('Please enter a name.', 'warn'); return; }
const isFolder = modalType === 'folder';
const cfg = isFolder ? {
@@ -584,7 +578,7 @@ function saveConnector() {
id: 'cn_' + Date.now(),
type: modalType,
name,
icon: isFolder ? '📁' : '🗄️',
icon: isFolder ? ICONS.folder : ICONS.database,
status: 'idle',
config: cfg,
stats: { files:0, rowsRead:0, lastSync:null, errors:0 }
@@ -594,7 +588,7 @@ function saveConnector() {
persist();
closeModal();
renderAll();
toast('✓ Connector saved');
showToast('Connector saved', 'success');
}
function removeConnector(id) {
@@ -602,7 +596,7 @@ function removeConnector(id) {
connectors = connectors.filter(c => c.id !== id);
persist();
renderAll();
toast('Connector removed');
showToast('Connector removed', 'success');
}
// ── Sync ──────────────────────────────────────────────────────────────────────
@@ -623,14 +617,11 @@ async function syncNow(id) {
cn.stats.errors = 0;
addLog('ok', `[${cn.name}] Sync complete — ${result.files||0} files indexed`);
} catch(e) {
// Mock success
await sleep(1200 + Math.random()*800);
const added = Math.floor(Math.random()*8) + 1;
cn.status = 'ok';
cn.stats.files = (cn.stats.files||0) + added;
cn.stats.lastSync = new Date().toISOString();
cn.stats.errors = 0;
addLog('ok', `[${cn.name}] Sync complete — ${added} new file${added>1?'s':''} indexed`);
cn.status = 'error';
cn.stats.errors = (cn.stats.errors||0) + 1;
cn.stats.lastError = e.message || 'Sync request failed — could not reach the API';
addLog('error', `[${cn.name}] Sync failed — ${cn.stats.lastError}`);
showToast(`Sync failed for ${cn.name}: ${cn.stats.lastError}`, 'error');
}
persist();
@@ -662,13 +653,13 @@ async function testConnection() {
const path = document.getElementById('m-path')?.value || document.getElementById('m-host')?.value;
if (!path) {
result.className = 'cn-test-result err';
result.textContent = `✗ ${isFolder ? 'No folder path specified' : 'No host specified'}`;
result.textContent = isFolder ? 'Folder path is required' : 'Host is required';
return;
}
result.className = 'cn-test-result ok';
result.textContent = isFolder
? `✓ Path accessible — permissions OK, Nexus One AI can read this location`
: `✓ Connected to ${document.getElementById('m-driver')?.value?.toUpperCase()||'DB'} — ${document.getElementById('m-tables')?.value?.split(',').length||0} tables verified (read-only)`;
? 'Path accessible. Permissions look correct and Nexus One AI can read this location.'
: `Connected to ${document.getElementById('m-driver')?.value?.toUpperCase()||'DB'}. ${document.getElementById('m-tables')?.value?.split(',').length||0} tables verified in read-only mode.`;
}
// ── Log ───────────────────────────────────────────────────────────────────────
@@ -681,21 +672,7 @@ async function loadLog() {
`<div class="cn-log-line ${l.level||''}">[${new Date(l.ts).toLocaleTimeString([],{hour:'2-digit',minute:'2-digit',second:'2-digit'})}] ${escHtml(l.msg)}</div>`
).join('') || '<div class="cn-log-line info">— No activity yet —</div>';
} catch(e) {
// Mock log
const mockLines = [
{ level:'info', ts: new Date(Date.now()-5*60000).toISOString(), msg:'[HR Documents Share] Sync started' },
{ level:'ok', ts: new Date(Date.now()-4*60000).toISOString(), msg:'[HR Documents Share] 3 new files indexed (leave_policy_2026.pdf, org_chart_q2.xlsx, onboarding_checklist.docx)' },
{ level:'ok', ts: new Date(Date.now()-4*60000).toISOString(), msg:'[HR Documents Share] Sync complete — 142 files total' },
{ level:'info', ts: new Date(Date.now()-62*60000).toISOString(), msg:'[Finance Reports] Sync started' },
{ level:'ok', ts: new Date(Date.now()-61*60000).toISOString(), msg:'[Finance Reports] No changes detected (67 files unchanged)' },
{ level:'err', ts: new Date(Date.now()-2*3600000).toISOString(), msg:'[Legal Contracts] Mount failed: cannot access \\\\lawserver\\contracts — Connection refused' },
{ level:'warn', ts: new Date(Date.now()-2*3600000).toISOString(), msg:'[Legal Contracts] Retrying in 15 minutes (attempt 3/5)' },
{ level:'info', ts: new Date(Date.now()-3*3600000).toISOString(), msg:'[ERP Database] Schema refresh — 3 tables, 12,400 rows indexed' },
{ level:'ok', ts: new Date(Date.now()-3*3600000).toISOString(), msg:'[ERP Database] Connection OK — PostgreSQL 15.2' }
];
el.innerHTML = mockLines.map(l =>
`<div class="cn-log-line ${l.level}">[${new Date(l.ts).toLocaleTimeString([],{hour:'2-digit',minute:'2-digit',second:'2-digit'})}] ${escHtml(l.msg)}</div>`
).join('');
el.innerHTML = '<div class="cn-log-line err">Couldn\'t reach the API to load the sync log. This is not showing example data — check API connectivity and reload.</div>';
}
el.scrollTop = el.scrollHeight;
}
@@ -730,12 +707,6 @@ function fmtAgo(d) {
function sleep(ms) { return new Promise(r => setTimeout(r, ms)); }
function toast(msg) {
const el = document.getElementById('cn-toast');
el.textContent = msg; el.classList.add('show');
setTimeout(() => el.classList.remove('show'), 2200);
}
function escHtml(s) { return (s||'').replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;'); }
function escAttr(s) { return (s||'').replace(/"/g,'&quot;').replace(/</g,'&lt;'); }
@@ -743,6 +714,6 @@ init();
</script>
<script src="auth.js"></script>
<script src="branding.js"></script>
<script src="branding.js?v=3"></script>
</body>
</html>