Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions app/web/lib/analyzer.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ function getSubnet24(ip) {
}

// Longest-prefix match against knownSubnets (sorted most-specific first).
// Falls back to /24 if no match or no knownSubnets provided.
// Keeps the observed IP as a /32 host when no FortiGate network matches.
// knownSubnets = [{ prefix: Number, networkInt: Number, cidr: String }]
function getSubnetForIP(ip, knownSubnets) {
if (knownSubnets && knownSubnets.length > 0) {
Expand All @@ -46,7 +46,7 @@ function getSubnetForIP(ip, knownSubnets) {
}
}
}
return getSubnet24(ip);
return isNaN(ip2int(ip)) ? null : `${ip}/32`;
}

// ─── Proto labels ─────────────────────────────────────────────────────────────
Expand Down
970 changes: 922 additions & 48 deletions app/web/lib/forticonfig.js

Large diffs are not rendered by default.

746 changes: 508 additions & 238 deletions app/web/public/app.js

Large diffs are not rendered by default.

73 changes: 63 additions & 10 deletions app/web/public/style.css
Original file line number Diff line number Diff line change
Expand Up @@ -215,7 +215,7 @@ html, body {

/* ── Content area ───────────────────────────────────────────────────────────── */

#content { flex: 1; padding: 36px 40px; overflow-y: auto; }
#content { flex: 1; padding: 24px 28px; overflow-y: auto; }

/* ── Upload zone ────────────────────────────────────────────────────────────── */

Expand Down Expand Up @@ -521,9 +521,13 @@ td.mono { font-family: var(--mono); }
border-radius: var(--radius);
background: var(--bg1);
position: relative;
display: grid;
place-items: start center;
padding: 28px;
min-height: 380px;
}

#matrix-canvas { display: block; image-rendering: pixelated; }
#matrix-canvas { display: block; image-rendering: auto; }

#matrix-tooltip {
display: none;
Expand Down Expand Up @@ -879,12 +883,12 @@ td.mono { font-family: var(--mono); }

/* ── Deploy view ─────────────────────────────────────────────────────────────── */

.deploy-wrap { display: flex; flex-direction: column; gap: 20px; padding: 32px 36px; }
.deploy-wrap { display: flex; flex-direction: column; gap: 14px; padding: 0; }

.deploy-step { background: var(--bg1); border: 1px solid var(--border); border-radius: 6px; min-width: 0; }

.deploy-step-header {
display: flex; align-items: center; gap: 12px; padding: 14px 20px;
display: flex; align-items: center; gap: 12px; padding: 12px 16px;
background: var(--bg2); border-bottom: 1px solid var(--border);
font-size: 13px; font-weight: 600; color: var(--text); letter-spacing: 0.1px;
}
Expand All @@ -895,7 +899,7 @@ td.mono { font-family: var(--mono); }
display: flex; align-items: center; justify-content: center; flex-shrink: 0;
}

.deploy-step-body { padding: 18px 20px; }
.deploy-step-body { padding: 14px 16px; }

.deploy-step-footer {
padding: 14px 20px; border-top: 1px solid var(--border);
Expand Down Expand Up @@ -1032,11 +1036,42 @@ td.mono { font-family: var(--mono); }

/* Policy table */
.deploy-policy-table { width: 100%; border-collapse: collapse; font-size: 12px; }
.deploy-policy-table th, .deploy-policy-table td { padding: 7px 9px; text-align: left; border-bottom: 1px solid var(--border); white-space: nowrap; }
.deploy-policy-table th, .deploy-policy-table td { padding: 7px 9px; text-align: left; border-bottom: 1px solid var(--border); vertical-align: middle; }
.deploy-policy-table th { color: var(--text2); font-weight: 700; font-size: 9.5px; background: var(--bg2); text-transform: uppercase; letter-spacing: 0.8px; }
.deploy-policy-table th.sortable-th:hover { color: var(--text1); background: var(--bg3, var(--bg2)); }
.deploy-policy-table th.sort-active { color: var(--accent, #4f8ef7); }
.deploy-policy-row:hover { background: var(--bg2); }
.policy-controls-cell { width: 116px; white-space: nowrap; display: table-cell; }
.policy-controls-cell > * { margin-right: 6px; vertical-align: middle; }
.policy-controls-cell .deploy-merge-chk,
.policy-controls-cell .policy-row-secondary { opacity: 0; transition: opacity 0.15s; }
.deploy-policy-row:hover .deploy-merge-chk,
.deploy-policy-row:hover .policy-row-secondary,
.policy-controls-cell .deploy-merge-chk:checked { opacity: 1; }
.policy-main-cell { min-width: 170px; white-space: nowrap !important; }
.policy-primary-line { display: flex; align-items: center; gap: 6px; min-width: 0; }
.policy-primary-value { font-family: var(--mono); font-size: 12px; color: var(--text); }
.policy-session-inline { margin-left: auto; color: var(--text2); font-family: var(--mono); font-size: 10px; }
.policy-services-cell { min-width: 250px; max-width: 390px; white-space: nowrap !important; overflow: hidden; text-overflow: ellipsis; line-height: 1.6; }
.compact-more { display: inline-block; color: var(--text2); background: var(--bg3); border: 1px solid var(--border); border-radius: 10px; padding: 0 7px; font-size: 10px; white-space: nowrap; }
.policy-interfaces-cell { min-width: 190px; white-space: normal !important; }
.policy-interface-pair { display: grid; grid-template-columns: minmax(0,1fr) 18px minmax(0,1fr); align-items: center; gap: 4px; }
.policy-interface-pair > span:not(.policy-interface-arrow) { min-width: 0; overflow: hidden; text-overflow: ellipsis; }
.policy-interface-arrow { text-align: center; color: var(--text3); }
.policy-objects-cell { min-width: 270px; max-width: 390px; white-space: nowrap !important; overflow: hidden; }
.policy-object-pair { display: grid; grid-template-columns: minmax(0,1fr) 18px minmax(0,1fr); align-items: center; gap: 4px; min-width: 0; }
.policy-object-pair .inline-editable { max-width: none; min-width: 0; display: flex; align-items: center; overflow: visible; }
.policy-object-pair .object-name { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.policy-object-pair .object-status-tag { flex-shrink: 0; }
.policy-state-cell { width: 90px; text-align: center !important; }
.policy-ready, .policy-needs-work { display: inline-block; padding: 3px 8px; border-radius: 10px; font-size: 10px; font-weight: 700; white-space: nowrap; }
.policy-ready { color: var(--success); background: rgba(74,158,114,0.1); border: 1px solid rgba(74,158,114,0.25); }
.policy-needs-work { color: var(--warn); background: rgba(200,133,58,0.1); border: 1px solid rgba(200,133,58,0.25); }
.object-status-tag { display: inline-block; margin-left: 5px; padding: 1px 5px; border-radius: 3px; font-size: 8px; font-weight: 700; letter-spacing: 0.3px; vertical-align: middle; white-space: nowrap; }
.object-status-tag.exact { color: var(--success); background: rgba(74,158,114,0.12); border: 1px solid rgba(74,158,114,0.25); }
.object-status-tag.create { color: var(--brand); background: rgba(192,112,136,0.12); border: 1px solid rgba(192,112,136,0.28); }
.object-status-tag.auto { color: var(--accent2); background: rgba(107,158,232,0.12); border: 1px solid rgba(107,158,232,0.25); }
.object-status-tag.broad { color: var(--warn); background: rgba(200,133,58,0.12); border: 1px solid rgba(200,133,58,0.28); }
.policy-deny-row { background: rgba(239,68,68,0.08) !important; }
.policy-deny-row:hover { background: rgba(239,68,68,0.15) !important; }
.policy-deny-row td { border-bottom-color: rgba(239,68,68,0.2); }
Expand Down Expand Up @@ -1354,7 +1389,7 @@ td:has([data-tip]) { overflow: visible; }
}
.policy-drawer-overlay.open { opacity: 1; pointer-events: auto; }
.policy-drawer {
position: fixed; top: 0; right: -460px; width: 440px; height: 100vh;
position: fixed; top: 0; right: -560px; width: min(520px, 94vw); height: 100vh;
background: var(--bg1); border-left: 1px solid var(--border2);
box-shadow: -8px 0 40px rgba(0,0,0,0.5); z-index: 901;
transition: right 0.25s ease; display: flex; flex-direction: column;
Expand All @@ -1368,8 +1403,8 @@ td:has([data-tip]) { overflow: visible; }
.drawer-header h3 { font-size: 13px; font-weight: 700; margin: 0; flex: 1; }
.drawer-close { background: none; border: none; color: var(--text2); font-size: 18px; cursor: pointer; padding: 4px 8px; border-radius: 4px; }
.drawer-close:hover { background: var(--bg3); color: var(--text); }
.drawer-body { flex: 1; overflow-y: auto; padding: 16px 20px; }
.drawer-section { margin-bottom: 20px; }
.drawer-body { flex: 1; overflow-y: auto; padding: 14px 18px 24px; }
.drawer-section { margin-bottom: 16px; }
.drawer-section-title {
font-size: 10px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.8px;
color: var(--text2); margin-bottom: 8px; padding-bottom: 4px; border-bottom: 1px solid var(--border);
Expand Down Expand Up @@ -1408,6 +1443,20 @@ td:has([data-tip]) { overflow: visible; }
border-bottom: 1px solid var(--border);
}
.drawer-multidst-subnet { font-size: 10px; font-family: var(--mono); min-width: 110px; }
.drawer-more-toggle { width: 100%; margin-top: 6px; padding: 6px 10px; border: 1px solid var(--border); border-radius: 4px; background: var(--bg2); color: var(--accent2); font-size: 10px; cursor: pointer; }
.drawer-more-toggle:hover { background: var(--bg3); border-color: var(--border2); }
.drawer-interface-field { margin-bottom: 6px; }
.drawer-object-field { margin-bottom: 6px; }
.drawer-general-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 8px 12px; }
.drawer-general-grid > div { min-height: 34px; display: flex; align-items: center; gap: 7px; padding: 6px 8px; border: 1px solid var(--border); border-radius: 4px; background: var(--bg2); }
.drawer-general-grid > div > span:first-child { color: var(--text2); font-size: 10px; margin-right: auto; }
.drawer-general-grid strong { font-family: var(--mono); font-size: 11px; color: var(--text); }
.drawer-general-action { grid-column: 1 / -1; }
.drawer-advanced { margin-top: 4px; border: 1px solid var(--border); border-radius: 6px; background: var(--bg2); }
.drawer-advanced > summary { cursor: pointer; list-style: none; padding: 10px 12px; color: var(--text2); font-size: 10px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.7px; }
.drawer-advanced > summary::after { content: '▾'; float: right; color: var(--text3); }
.drawer-advanced[open] > summary::after { content: '▴'; }
.drawer-advanced-body { padding: 10px 12px 4px; border-top: 1px solid var(--border); }

/* ── Inline editable cell (click-to-edit) ────────────────────────────────────── */
.inline-editable {
Expand Down Expand Up @@ -1523,9 +1572,13 @@ td:has([data-tip]) { overflow: visible; }
.dropdown-item:first-child { border-radius: var(--radius) var(--radius) 0 0; }
.dropdown-item:last-child { border-radius: 0 0 var(--radius) var(--radius); }
.dropdown-sep { height: 1px; background: var(--border); margin: 4px 0; }
.deploy-options-menu { left: auto; right: 0; min-width: 210px; padding: 8px 10px; }
.deploy-option-row { display: flex; align-items: center; justify-content: space-between; gap: 16px; padding: 7px 4px; font-size: 11px; color: var(--text2); }
.deploy-option-row + .deploy-option-row { border-top: 1px solid var(--border); }
.deploy-option-row .deploy-select { min-width: 104px; }

/* ── Wizard progress ─────────────────────────────────────────────────────────── */
.wizard-progress { display: flex; align-items: center; justify-content: center; gap: 0; padding: 24px 28px; margin-bottom: 8px; }
.wizard-progress { display: flex; align-items: center; justify-content: center; gap: 0; padding: 12px 18px; margin-bottom: 0; }
.wizard-step-indicator { display: flex; align-items: center; gap: 8px; font-size: 12px; color: var(--text2); opacity: 0.3; cursor: pointer; user-select: none; transition: opacity 0.2s; }
.wizard-step-indicator.active { opacity: 1; color: var(--text); }
.wizard-step-indicator.done { opacity: 1; color: var(--brand); }
Expand Down
Loading