|
|
@@ -5948,9 +5948,52 @@ function renderRoutesList() {
|
|
|
|
|
|
const isDefault = r.dst_address === 'default' || r.dst_address === '0.0.0.0/0';
|
|
|
|
|
|
+ let healthCell = `<span class="text-slate-600 font-mono text-[11px]">-</span>`;
|
|
|
+ if (r.check_gateway === 'internet') {
|
|
|
+ if (r.health_status === 'online') {
|
|
|
+ const ms = (r.health_rtt_ms || 0).toFixed(0);
|
|
|
+ healthCell = `
|
|
|
+ <span class="px-2 py-0.5 rounded text-[10px] font-mono bg-emerald-500/20 text-emerald-300 border border-emerald-500/30 font-semibold inline-flex items-center gap-1.5" title="Internet Verified via ${escapeHtml(r.check_target || '1.1.1.1')} (${r.health_loss_pct || 0}% loss)">
|
|
|
+ <span class="w-1.5 h-1.5 rounded-full bg-emerald-400 animate-pulse"></span>
|
|
|
+ <span>Internet OK (${ms}ms)</span>
|
|
|
+ </span>
|
|
|
+ `;
|
|
|
+ } else if (r.health_status === 'offline') {
|
|
|
+ healthCell = `
|
|
|
+ <span class="px-2 py-0.5 rounded text-[10px] font-mono bg-rose-500/20 text-rose-300 border border-rose-500/40 font-bold inline-flex items-center gap-1.5 animate-pulse" title="Internet probe to ${escapeHtml(r.check_target || '1.1.1.1')} failed! Route withdrawn for failover.">
|
|
|
+ <span class="w-1.5 h-1.5 rounded-full bg-rose-400"></span>
|
|
|
+ <span>Internet Down</span>
|
|
|
+ </span>
|
|
|
+ `;
|
|
|
+ } else {
|
|
|
+ healthCell = `
|
|
|
+ <span class="px-2 py-0.5 rounded text-[10px] font-mono bg-slate-800 text-slate-400 border border-slate-700 inline-flex items-center gap-1.5">
|
|
|
+ <span class="w-1.5 h-1.5 rounded-full bg-amber-400 animate-ping"></span>
|
|
|
+ <span>Checking...</span>
|
|
|
+ </span>
|
|
|
+ `;
|
|
|
+ }
|
|
|
+ } else if (r.check_gateway === 'ping') {
|
|
|
+ if (r.health_status === 'online') {
|
|
|
+ healthCell = `
|
|
|
+ <span class="px-2 py-0.5 rounded text-[10px] font-mono bg-cyan-500/20 text-cyan-300 border border-cyan-500/30 font-semibold inline-flex items-center gap-1.5">
|
|
|
+ <span class="w-1.5 h-1.5 rounded-full bg-cyan-400"></span>
|
|
|
+ <span>Gateway OK (${(r.health_rtt_ms || 0).toFixed(0)}ms)</span>
|
|
|
+ </span>
|
|
|
+ `;
|
|
|
+ } else if (r.health_status === 'offline') {
|
|
|
+ healthCell = `
|
|
|
+ <span class="px-2 py-0.5 rounded text-[10px] font-mono bg-rose-500/20 text-rose-300 border border-rose-500/40 font-bold inline-flex items-center gap-1.5">
|
|
|
+ <span class="w-1.5 h-1.5 rounded-full bg-rose-400"></span>
|
|
|
+ <span>Gateway Dead</span>
|
|
|
+ </span>
|
|
|
+ `;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
tr.innerHTML = `
|
|
|
<td class="py-2.5 px-3 text-center">
|
|
|
- <span class="px-1.5 py-0.5 rounded text-[10px] font-mono font-bold bg-slate-800 ${r.active ? 'text-cyan-400 border border-cyan-800/40' : 'text-slate-500'}">
|
|
|
+ <span class="px-1.5 py-0.5 rounded text-[10px] font-mono font-bold bg-slate-800 ${r.active ? 'text-cyan-400 border border-cyan-800/40' : (r.health_status === 'offline' ? 'text-rose-400 border border-rose-800/40' : 'text-slate-500')}">
|
|
|
${flags || '-'}
|
|
|
</span>
|
|
|
</td>
|
|
|
@@ -5959,6 +6002,7 @@ function renderRoutesList() {
|
|
|
<td class="py-2.5 px-3 text-slate-400 font-mono">${(r.distance !== undefined && r.distance !== null) ? r.distance : 0}</td>
|
|
|
<td class="py-2.5 px-3 font-mono font-semibold text-purple-300">${r.routing_table || 'main'}</td>
|
|
|
<td class="py-2.5 px-3 text-emerald-400 font-mono">${r.interface || '-'}</td>
|
|
|
+ <td class="py-2.5 px-3">${healthCell}</td>
|
|
|
<td class="py-2.5 px-3 text-slate-400 italic truncate max-w-[140px]">${r.comment || '-'}</td>
|
|
|
<td class="py-2.5 px-3 text-right space-x-2 font-sans whitespace-nowrap">
|
|
|
${r.static ? `
|
|
|
@@ -6006,6 +6050,16 @@ function openAddRouteModal() {
|
|
|
disCheck.checked = false;
|
|
|
if (saveBtn) saveBtn.classList.remove('hidden');
|
|
|
|
|
|
+ const checkGw = document.getElementById('route-modal-check-gw');
|
|
|
+ if (checkGw) checkGw.value = 'internet';
|
|
|
+ const checkTarget = document.getElementById('route-modal-check-target');
|
|
|
+ if (checkTarget) checkTarget.value = '1.1.1.1';
|
|
|
+ const checkInterval = document.getElementById('route-modal-check-interval');
|
|
|
+ if (checkInterval) checkInterval.value = '5';
|
|
|
+ const checkThresh = document.getElementById('route-modal-check-threshold');
|
|
|
+ if (checkThresh) checkThresh.value = '3';
|
|
|
+ toggleRouteCheckModeUI();
|
|
|
+
|
|
|
populateInterfaceSelect('route-modal-iface');
|
|
|
|
|
|
// Populate tables
|
|
|
@@ -6052,6 +6106,16 @@ function openEditRouteModal(id) {
|
|
|
commentInput.value = r.comment || '';
|
|
|
disCheck.checked = !!r.disabled;
|
|
|
|
|
|
+ const checkGw = document.getElementById('route-modal-check-gw');
|
|
|
+ if (checkGw) checkGw.value = r.check_gateway || 'none';
|
|
|
+ const checkTarget = document.getElementById('route-modal-check-target');
|
|
|
+ if (checkTarget) checkTarget.value = r.check_target || '1.1.1.1';
|
|
|
+ const checkInterval = document.getElementById('route-modal-check-interval');
|
|
|
+ if (checkInterval) checkInterval.value = r.check_interval || 5;
|
|
|
+ const checkThresh = document.getElementById('route-modal-check-threshold');
|
|
|
+ if (checkThresh) checkThresh.value = r.check_threshold || 3;
|
|
|
+ toggleRouteCheckModeUI();
|
|
|
+
|
|
|
dstInput.disabled = !isStatic;
|
|
|
gwInput.disabled = !isStatic;
|
|
|
distInput.disabled = !isStatic;
|
|
|
@@ -6059,6 +6123,10 @@ function openEditRouteModal(id) {
|
|
|
tableSelect.disabled = !isStatic;
|
|
|
commentInput.disabled = !isStatic;
|
|
|
disCheck.disabled = !isStatic;
|
|
|
+ if (checkGw) checkGw.disabled = !isStatic;
|
|
|
+ if (checkTarget) checkTarget.disabled = !isStatic;
|
|
|
+ if (checkInterval) checkInterval.disabled = !isStatic;
|
|
|
+ if (checkThresh) checkThresh.disabled = !isStatic;
|
|
|
|
|
|
if (saveBtn) {
|
|
|
if (isStatic) {
|
|
|
@@ -6086,6 +6154,28 @@ function closeRouteModal() {
|
|
|
document.getElementById('route-modal').classList.add('hidden');
|
|
|
}
|
|
|
|
|
|
+function setRouteCheckTarget(ip) {
|
|
|
+ const inp = document.getElementById('route-modal-check-target');
|
|
|
+ if (inp) inp.value = ip;
|
|
|
+}
|
|
|
+
|
|
|
+function toggleRouteCheckModeUI() {
|
|
|
+ const mode = document.getElementById('route-modal-check-gw')?.value || 'none';
|
|
|
+ const targetRow = document.getElementById('route-row-check-target');
|
|
|
+ const paramsRow = document.getElementById('route-row-check-params');
|
|
|
+
|
|
|
+ if (mode === 'none') {
|
|
|
+ if (targetRow) targetRow.classList.add('opacity-40', 'pointer-events-none');
|
|
|
+ if (paramsRow) paramsRow.classList.add('opacity-40', 'pointer-events-none');
|
|
|
+ } else if (mode === 'ping') {
|
|
|
+ if (targetRow) targetRow.classList.add('opacity-40', 'pointer-events-none');
|
|
|
+ if (paramsRow) paramsRow.classList.remove('opacity-40', 'pointer-events-none');
|
|
|
+ } else { // internet
|
|
|
+ if (targetRow) targetRow.classList.remove('opacity-40', 'pointer-events-none');
|
|
|
+ if (paramsRow) paramsRow.classList.remove('opacity-40', 'pointer-events-none');
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
async function submitSaveRoute(e) {
|
|
|
e.preventDefault();
|
|
|
const id = document.getElementById('route-modal-id').value;
|
|
|
@@ -6103,6 +6193,11 @@ async function submitSaveRoute(e) {
|
|
|
const comment = document.getElementById('route-modal-comment').value.trim();
|
|
|
const disabled = document.getElementById('route-modal-disabled').checked;
|
|
|
|
|
|
+ const check_gateway = document.getElementById('route-modal-check-gw')?.value || 'none';
|
|
|
+ const check_target = document.getElementById('route-modal-check-target')?.value.trim() || '1.1.1.1';
|
|
|
+ const check_interval = parseInt(document.getElementById('route-modal-check-interval')?.value, 10) || 5;
|
|
|
+ const check_threshold = parseInt(document.getElementById('route-modal-check-threshold')?.value, 10) || 3;
|
|
|
+
|
|
|
if (!dst) {
|
|
|
showToast('Error', 'Destination address is required', 'error');
|
|
|
return;
|
|
|
@@ -6124,7 +6219,11 @@ async function submitSaveRoute(e) {
|
|
|
distance,
|
|
|
interface: iface,
|
|
|
comment,
|
|
|
- disabled
|
|
|
+ disabled,
|
|
|
+ check_gateway,
|
|
|
+ check_target,
|
|
|
+ check_interval,
|
|
|
+ check_threshold
|
|
|
};
|
|
|
|
|
|
if (oldDistStr !== undefined && oldDistStr !== '') {
|