body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
margin: 0;
padding: 0;
}
.calc-container {
max-width: 900px;
margin: 20px auto;
padding: 20px;
}
/* ── Header ── */
.calc-header {
margin-bottom: 30px;
padding: 25px;
background: linear-gradient(135deg, #2196F3 0%, #1976D2 100%);
border-radius: 12px;
box-shadow: 0 4px 6px rgba(0,0,0,0.1);
color: white;
}
.calc-header h2 {
margin-top: 0;
color: white;
font-size: 28px;
margin-bottom: 10px;
}
.calc-header p {
margin: 10px 0 20px 0;
opacity: 0.95;
font-size: 15px;
}
/* ── Form controls inside header ── */
.form-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 15px;
margin-top: 20px;
}
.form-group {
display: flex;
flex-direction: column;
gap: 8px;
}
.form-group label {
font-weight: 600;
font-size: 14px;
color: white;
}
.form-group input,
.form-group select {
padding: 12px;
font-size: 15px;
border: 2px solid rgba(255,255,255,0.3);
border-radius: 6px;
background: rgba(255,255,255,0.95);
transition: all 0.3s ease;
font-family: 'Courier New', monospace;
}
.form-group input:focus,
.form-group select:focus {
outline: none;
border-color: #4CAF50;
background: white;
box-shadow: 0 0 0 3px rgba(76,175,80,0.15);
}
.form-group select {
cursor: pointer;
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
}
/* ── Buttons ── */
.btn {
padding: 12px 24px;
font-size: 15px;
border: none;
border-radius: 6px;
cursor: pointer;
font-weight: bold;
transition: all 0.3s ease;
box-shadow: 0 2px 4px rgba(0,0,0,0.2);
}
.btn-primary {
background: #4CAF50;
color: white;
}
.btn-primary:hover {
background: #45a049;
transform: translateY(-1px);
box-shadow: 0 4px 8px rgba(0,0,0,0.25);
}
.btn-primary:active {
background: #3d8b40;
transform: translateY(0);
}
.btn-secondary {
background: rgba(255,255,255,0.2);
color: white;
border: 2px solid rgba(255,255,255,0.4);
}
.btn-secondary:hover {
background: rgba(255,255,255,0.3);
transform: translateY(-1px);
}
.btn-danger {
background: #f44336;
color: white;
}
.btn-danger:hover {
background: #d32f2f;
transform: translateY(-1px);
}
.btn-sm {
padding: 6px 14px;
font-size: 13px;
}
.submit-btn {
padding: 14px 35px;
font-size: 16px;
background: #4CAF50;
color: white;
border: none;
border-radius: 6px;
cursor: pointer;
font-weight: bold;
transition: all 0.3s ease;
box-shadow: 0 2px 4px rgba(0,0,0,0.2);
margin-top: 15px;
width: 100%;
}
.submit-btn:hover {
background: #45a049;
transform: translateY(-1px);
box-shadow: 0 4px 8px rgba(0,0,0,0.3);
}
.submit-btn:active {
background: #3d8b40;
transform: translateY(0);
}
/* ── Result sections ── */
.result-section {
background: white;
border: 1px solid #e0e0e0;
border-radius: 8px;
margin-top: 20px;
overflow: hidden;
box-shadow: 0 2px 4px rgba(0,0,0,0.05);
transition: all 0.3s ease;
display: none;
}
.result-section.show {
display: block;
}
.result-section:hover {
box-shadow: 0 4px 8px rgba(0,0,0,0.1);
}
.section-header {
background: linear-gradient(135deg, #4CAF50 0%, #45a049 100%);
color: white;
padding: 15px 20px;
}
.section-header h3 {
margin: 0;
font-size: 18px;
display: flex;
align-items: center;
gap: 10px;
}
.section-header.blue {
background: linear-gradient(135deg, #2196F3 0%, #1976D2 100%);
}
.section-header.orange {
background: linear-gradient(135deg, #FF9800 0%, #F57C00 100%);
}
.section-header.purple {
background: linear-gradient(135deg, #9C27B0 0%, #7B1FA2 100%);
}
.section-content {
padding: 20px;
background: #fafafa;
}
.data-row {
display: flex;
margin: 10px 0;
padding: 10px;
background: white;
border-radius: 4px;
border-left: 3px solid #4CAF50;
align-items: flex-start;
}
.data-label {
font-weight: bold;
color: #555;
min-width: 200px;
flex-shrink: 0;
font-size: 14px;
padding-top: 1px;
}
.data-value {
color: #333;
font-family: 'Courier New', monospace;
word-break: break-word;
font-size: 15px;
}
.data-value.highlight {
color: #1976D2;
font-weight: bold;
font-size: 18px;
}
/* ── Help text ── */
.help-text {
margin-top: 15px;
font-size: 14px;
color: rgba(255,255,255,0.9);
background: rgba(0,0,0,0.1);
padding: 10px 15px;
border-radius: 4px;
}
.help-text strong {
color: white;
}
/* ── Topology canvas area ── */
#cy-container {
width: 100%;
height: 380px;
background: #1a2332;
border-radius: 8px;
border: 2px solid #2d3d52;
position: relative;
overflow: hidden;
}
#cy {
width: 100%;
height: 100%;
}
/* ── Switch list ── */
.switch-list,
.link-list {
list-style: none;
margin: 0;
padding: 0;
}
.switch-item,
.link-item {
display: flex;
align-items: center;
gap: 10px;
padding: 8px 12px;
background: white;
border-radius: 5px;
margin-bottom: 6px;
border-left: 4px solid #2196F3;
font-size: 14px;
flex-wrap: wrap;
}
.switch-item .sw-id {
font-family: 'Courier New', monospace;
font-weight: bold;
color: #1976D2;
min-width: 40px;
}
.switch-item .sw-name {
flex: 1;
font-weight: 600;
}
.switch-item .sw-priority,
.switch-item .sw-mac {
font-family: 'Courier New', monospace;
font-size: 13px;
color: #666;
}
.link-item {
border-left-color: #FF9800;
}
.link-item .lk-label {
flex: 1;
font-family: 'Courier New', monospace;
font-size: 13px;
}
.tag {
display: inline-block;
padding: 2px 8px;
border-radius: 12px;
font-size: 12px;
font-weight: bold;
}
.tag-root { background: #FFF3CD; color: #856404; }
.tag-fwd { background: #D4EDDA; color: #155724; }
.tag-blk { background: #F8D7DA; color: #721C24; }
.tag-disc { background: #D1ECF1; color: #0C5460; }
.tag-lrn { background: #CCE5FF; color: #004085; }
.tag-edge { background: #E2D9F3; color: #491F8A; }
/* ── Port state table ── */
.port-table {
width: 100%;
border-collapse: collapse;
font-size: 14px;
margin-top: 8px;
}
.port-table th {
background: #E3F2FD;
color: #1565C0;
padding: 8px 12px;
text-align: left;
font-weight: 600;
border-bottom: 2px solid #BBDEFB;
}
.port-table td {
padding: 7px 12px;
border-bottom: 1px solid #f0f0f0;
font-family: 'Courier New', monospace;
font-size: 13px;
}
.port-table tr:last-child td {
border-bottom: none;
}
.port-table tr:nth-child(even) td {
background: #f8f9fa;
}
/* ── Timeline ── */
.timeline {
position: relative;
padding-left: 28px;
}
.timeline::before {
content: '';
position: absolute;
left: 8px;
top: 0;
bottom: 0;
width: 2px;
background: #BBDEFB;
}
.timeline-event {
position: relative;
margin-bottom: 12px;
background: white;
border-radius: 6px;
padding: 10px 14px;
border: 1px solid #e8e8e8;
font-size: 13px;
}
.timeline-event::before {
content: '';
position: absolute;
left: -24px;
top: 12px;
width: 10px;
height: 10px;
border-radius: 50%;
background: #2196F3;
border: 2px solid white;
box-shadow: 0 0 0 2px #2196F3;
}
.timeline-event.root-election::before { background: #FFC107; box-shadow: 0 0 0 2px #FFC107; }
.timeline-event.port-change::before { background: #4CAF50; box-shadow: 0 0 0 2px #4CAF50; }
.timeline-event.port-blocked::before { background: #f44336; box-shadow: 0 0 0 2px #f44336; }
.timeline-event.convergence::before { background: #9C27B0; box-shadow: 0 0 0 2px #9C27B0; }
.timeline-event .te-time {
font-weight: bold;
color: #1976D2;
font-family: 'Courier New', monospace;
margin-right: 8px;
}
.timeline-event .te-desc {
color: #333;
}
/* ── BPDU Inspector ── */
.bpdu-panel {
background: #0d1117;
border-radius: 6px;
padding: 15px;
font-family: 'Courier New', monospace;
font-size: 13px;
color: #79c0ff;
max-height: 300px;
overflow-y: auto;
}
.bpdu-row {
display: flex;
gap: 8px;
margin-bottom: 3px;
line-height: 1.5;
}
.bpdu-field {
color: #d2a8ff;
min-width: 180px;
flex-shrink: 0;
}
.bpdu-val {
color: #a5f3fc;
}
.bpdu-val.root { color: #fbbf24; }
.bpdu-val.bridge { color: #86efac; }
/* ── Scenario selector ── */
.scenario-bar {
display: flex;
gap: 10px;
align-items: center;
flex-wrap: wrap;
margin-top: 10px;
}
.scenario-bar label {
font-weight: 600;
font-size: 14px;
color: white;
}
.scenario-bar select {
padding: 8px 12px;
border: 2px solid rgba(255,255,255,0.3);
border-radius: 6px;
background: rgba(255,255,255,0.95);
font-size: 14px;
cursor: pointer;
flex: 1;
min-width: 200px;
}
/* ── Legend ── */
.legend {
display: flex;
flex-wrap: wrap;
gap: 10px;
margin-top: 8px;
font-size: 13px;
}
.legend-item {
display: flex;
align-items: center;
gap: 5px;
}
.legend-dot {
width: 12px;
height: 12px;
border-radius: 50%;
flex-shrink: 0;
}
/* ── Inline error / info messages ── */
.msg {
padding: 10px 14px;
border-radius: 6px;
font-size: 14px;
margin-top: 8px;
}
.msg-error {
background: #FFEBEE;
color: #C62828;
border-left: 4px solid #f44336;
}
.msg-info {
background: #E3F2FD;
color: #1565C0;
border-left: 4px solid #2196F3;
}
.msg-success {
background: #E8F5E9;
color: #1B5E20;
border-left: 4px solid #4CAF50;
}
/* ── STP concept panel ── */
.concept-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 12px;
}
.concept-card {
background: white;
border-radius: 6px;
padding: 14px;
border-top: 3px solid #2196F3;
font-size: 13px;
}
.concept-card h4 {
margin: 0 0 6px 0;
color: #1976D2;
font-size: 14px;
}
.concept-card p {
margin: 0;
color: #555;
line-height: 1.5;
}
.concept-card.rstp {
border-top-color: #9C27B0;
}
.concept-card.rstp h4 {
color: #7B1FA2;
}
/* ── Mode toggle ── */
.mode-toggle {
display: flex;
gap: 0;
margin-top: 15px;
background: rgba(0,0,0,0.15);
border-radius: 6px;
padding: 4px;
}
.mode-btn {
flex: 1;
padding: 8px 16px;
border: none;
background: transparent;
color: rgba(255,255,255,0.7);
cursor: pointer;
border-radius: 4px;
font-size: 14px;
font-weight: 600;
transition: all 0.2s ease;
}
.mode-btn.active {
background: white;
color: #1976D2;
box-shadow: 0 1px 3px rgba(0,0,0,0.2);
}
/* ── Pulse animation for root bridge ── */
@keyframes rootPulse {
0% { box-shadow: 0 0 0 0 rgba(255, 193, 7, 0.7); }
70% { box-shadow: 0 0 0 10px rgba(255, 193, 7, 0); }
100% { box-shadow: 0 0 0 0 rgba(255, 193, 7, 0); }
}
@keyframes fadeInUp {
from { opacity: 0; transform: translateY(12px); }
to { opacity: 1; transform: translateY(0); }
}
.fade-in-up {
animation: fadeInUp 0.35s ease both;
}
/* ── Responsive ── */
@media (max-width: 768px) {
.form-grid {
grid-template-columns: 1fr;
}
.data-row {
flex-direction: column;
}
.data-label {
margin-bottom: 4px;
min-width: auto;
}
.concept-grid {
grid-template-columns: 1fr;
}
#cy-container {
height: 280px;
}
.port-table {
font-size: 12px;
}
.port-table th,
.port-table td {
padding: 6px 8px;
}
.mode-btn {
padding: 8px 8px;
font-size: 13px;
}
.scenario-bar {
flex-direction: column;
align-items: stretch;
}
.scenario-bar label {
display: block;
}
}
@media (max-width: 480px) {
.calc-header {
padding: 18px;
}
.calc-header h2 {
font-size: 22px;
}
.switch-item,
.link-item {
flex-wrap: wrap;
}
}
══════════════════════════════ ══════════════════════════════
HEADER / ΔΙΑΜΟΡΦΩΣΗ
══════════════════════════════ ══════════════════════════════
/.calc-header
══════════════════════════════ ══════════════════════════════
ΤΡΕΧΟΥΣΑ ΤΟΠΟΛΟΓΙΑ ΚΑΤΑΛΟΓΟΣ
══════════════════════════════ ══════════════════════════════
Διακόπτες
- Δεν έχουν προστεθεί ακόμη διακόπτες.
Εδαφος διά παιγνίδι γκολφ
- Δεν έχουν προστεθεί ακόμα σύνδεσμοι.
══════════════════════════════ ══════════════════════════════
ΟΠΤΙΚΟΠΟΙΗΣΗ ΤΟΠΟΛΟΓΙΑΣ
══════════════════════════════ ══════════════════════════════
Θρύλος
Θύρα αποκλεισμού / απόρριψης
Διακεκομμένη γραμμή = αποκλεισμένος σύνδεσμος
══════════════════════════════ ══════════════════════════════
ΕΚΛΟΓΗ ΡΙΖΙΚΗΣ ΓΕΦΥΡΑΣ
══════════════════════════════ ══════════════════════════════
══════════════════════════════ ══════════════════════════════
ΠΟΛΙΤΕΙΑ
══════════════════════════════ ══════════════════════════════
══════════════════════════════ ══════════════════════════════
ΕΠΙΘΕΩΡΗΤΗΣ BPDU
══════════════════════════════ ══════════════════════════════
══════════════════════════════ ══════════════════════════════
ΧΡΟΝΟΔΙΑΓΡΑΜΜΑ ΣΥΓΚΛΙΣΗΣ
══════════════════════════════ ══════════════════════════════
══════════════════════════════ ══════════════════════════════
ΕΝΝΟΙΕΣ STP
══════════════════════════════ ══════════════════════════════
Εκλογή Root Bridge
Ο διακόπτης με το χαμηλότερο Bridge ID κερδίζει. Bridge ID = Priority (προεπιλογή 32768) + διεύθυνση MAC. Οι δεσμοί προτεραιότητας πηγαίνουν σε αριθμητικά χαμηλότερο MAC. Μόνο μία γέφυρα ρίζας ανά VLAN.
Θύρα ρίζας
Κάθε μεταγωγέας χωρίς ρίζα επιλέγει μία θύρα ρίζας — τη θύρα με το χαμηλότερο αθροιστικό κόστος διαδρομής προς τη γέφυρα ρίζας. Οι δεσμοί διακόπτονται από το αναγνωριστικό γειτονικής γέφυρας και μετά το αναγνωριστικό θύρας.
Καθορισμένο Λιμάνι
Κάθε τμήμα δικτύου έχει ακριβώς μια Προσδιορισμένη θύρα — τη θύρα που προσφέρει την καλύτερη (χαμηλού κόστους) διαδρομή προς τη ρίζα. Όλες οι θύρες στη γέφυρα ρίζας έχουν καθοριστεί.
Αποκλεισμένες / Απόρριψη θυρών
Οποιαδήποτε θύρα δεν είναι ούτε ρίζα ούτε καθορισμένη θύρα μπλοκάρεται (STP) ή απορρίπτεται (RSTP). Αυτά διακόπτουν τους βρόχους Layer-2 διατηρώντας μια τοπολογία χωρίς βρόχο.
Θύρες άκρων RSTP (802.1w).
Οι θύρες άκρων RSTP συνδέονται με τερματικούς κεντρικούς υπολογιστές και όχι με άλλους διακόπτες. Μεταβαίνουν αμέσως στο Forwarding (χωρίς καθυστέρηση ακρόασης/μάθησης), που ισοδυναμεί με τη δυνατότητα PortFast του STP.
Ταχύτητα σύγκλισης RSTP
Η σύγκλιση STP διαρκεί 30–50 δευτερόλεπτα (Listening 15s + Learning 15s ανά θύρα). Το RSTP χρησιμοποιεί μια χειραψία Πρότασης/Συμφωνίας, που συγκλίνει σε λιγότερο από 2 δευτερόλεπτα ανά άλμα.
Κόστος διαδρομής (IEEE 802.1D)
10 Gbps = 2, 1 Gbps = 4, 100 Mbps = 19, 10 Mbps = 100, 1,5 Mbps = 1000. Χαμηλότερο κόστος = προτιμώμενη διαδρομή. Root bridge διαφημίζει κόστος 0? κάθε hop προσθέτει το κόστος του λιμανιού του.
BPDU (Μονάδα δεδομένων πρωτοκόλλου γέφυρας)
Τα BPDU φέρουν: Root Bridge ID, Root Path Cost, Sender Bridge ID, Port ID και χρονοδιακόπτες. Το Root Bridge δημιουργεί BPDU κάθε Hello Time (2 δευτερόλεπτα). Άλλες γέφυρες τα αναμεταδίδουν εξερχόμενα σε καθορισμένα λιμάνια.
/.calc-container
Cytoscape.js CDN