{"id":77,"date":"2025-11-26T05:52:27","date_gmt":"2025-11-26T05:52:27","guid":{"rendered":"https:\/\/ciphermq.com\/docs\/?page_id=77"},"modified":"2025-11-26T08:10:06","modified_gmt":"2025-11-26T08:10:06","slug":"ciphermq-secure-raft-cluster","status":"publish","type":"page","link":"https:\/\/ciphermq.com\/docs\/index.php\/ciphermq-secure-raft-cluster\/","title":{"rendered":"CipherMQ Secure Raft Cluster"},"content":{"rendered":"\n<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n    <meta charset=\"UTF-8\">\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n    <title>CipherMQ &#8211; Complete Secure Raft Animation<\/title>\n    <script src=\"https:\/\/cdn.tailwindcss.com\"><\/script>\n    <style>\n        @import url('https:\/\/fonts.googleapis.com\/css2?family=Inter:wght@400;500;600;700&display=swap');\n        * { margin: 0; padding: 0; box-sizing: border-box; }\n        html, body {\n            height: 100%; font-family: 'Inter', sans-serif;\n            background: linear-gradient(135deg, #0f172a 0%, #1e293b 100%);\n            color: #e2e8f0; overflow: hidden;\n            display: flex; justify-content: center; align-items: center; padding: 1rem;\n        }\n        .container { width: 100%; max-width: 1000px; height: 100%; max-height: 900px; display: flex; flex-direction: column; gap: 1rem; }\n        h1 { font-size: clamp(1.8rem, 4vw, 2.8rem); font-weight: 700; text-align: center;\n             background: linear-gradient(90deg, #60a5fa, #c084fc); -webkit-background-clip: text; -webkit-text-fill-color: transparent; }\n        .subtitle { font-size: clamp(0.9rem, 2.2vw, 1.2rem); text-align: center; opacity: 0.9; }\n        .graph-container {\n            flex: 1; min-height: 0; position: relative; background: rgba(255,255,255,0.06);\n            border-radius: 1.5rem; padding: clamp(1rem, 3vw, 2rem); box-shadow: 0 20px 50px rgba(0,0,0,0.5);\n            backdrop-filter: blur(12px); border: 1px solid rgba(255,255,255,0.1);\n            display: grid; grid-template-columns: repeat(3, 1fr); gap: clamp(1rem, 4vw, 3rem);\n            align-items: center; justify-items: center; overflow: visible !important;\n        }\n        .node {\n            width: clamp(100px, 20vw, 180px); height: clamp(100px, 20vw, 180px);\n            background: rgba(255,255,255,0.1); border-radius: 1.8rem; display: flex; flex-direction: column;\n            align-items: center; justify-content: center; border: 3px solid transparent; transition: all 0.6s ease;\n            position: relative; backdrop-filter: blur(10px); z-index: 10;\n        }\n        .node.candidate { border-color: #f59e0b; background: rgba(245,158,11,0.25); animation: pulse 2s infinite; }\n        .node.leader { border-color: #10b981; background: rgba(16,185,129,0.3); box-shadow: 0 0 40px rgba(16,185,129,0.5); }\n        .node.follower { border-color: #64748b; }\n        .node.down { opacity: 0.3; border-color: #ef4444; }\n        .node.recovering { border-color: #8b5cf6; animation: pulse 1.5s infinite; }\n        .tls-badge { position: absolute; top: -8px; right: -8px; background: #10b981; color: white;\n                     padding: 4px 10px; border-radius: 9999px; font-size: clamp(0.6rem, 1.5vw, 0.8rem);\n                     font-weight: 700; opacity: 0; transform: scale(0); transition: all 0.6s ease-out; z-index: 30; }\n        @keyframes pulse { 0%,100% { opacity: 1; } 50% { opacity: 0.7; } }\n        .node-id { font-size: clamp(1rem, 3vw, 1.6rem); font-weight: 700; margin: 0.3rem 0; }\n        .node-role, .term, .messages { font-size: clamp(0.65rem, 1.8vw, 0.85rem); margin: 0.2rem 0; }\n        .term, .messages { background: rgba(0,0,0,0.4); padding: 0.2rem 0.6rem; border-radius: 0.6rem; }\n        .msg {\n            position: absolute; color: white; padding: clamp(0.4rem, 1.5vw, 0.8rem) clamp(0.8rem, 2.5vw, 1.6rem);\n            border-radius: 9999px; font-size: clamp(0.7rem, 2vw, 0.9rem); font-weight: 700;\n            opacity: 0; z-index: 20; pointer-events: none; white-space: nowrap;\n            box-shadow: 0 8px 25px rgba(0,0,0,0.7); transition: all 0.2s ease; transform: translateY(-50%); max-width: 90vw;\n            overflow: hidden; text-overflow: ellipsis;\n        }\n        .vote { background: linear-gradient(45deg, #f59e0b, #d97706); }\n        .append { background: linear-gradient(45deg, #8b5cf6, #7c3aed); }\n        .ack { background: linear-gradient(45deg, #06b6d4, #0891b2); }\n        .action-button {\n            background: linear-gradient(135deg, #3b82f6, #1e40af); padding: clamp(0.7rem, 2vw, 1rem) clamp(2rem, 6vw, 4rem);\n            border-radius: 1rem; font-weight: 700; font-size: clamp(0.9rem, 2.5vw, 1.2rem);\n            cursor: pointer; transition: all 0.4s; box-shadow: 0 10px 30px rgba(59,130,246,0.4); align-self: center;\n        }\n        .action-button:hover { transform: translateY(-4px); }\n        .status-log { background: rgba(0,0,0,0.4); border-radius: 1.2rem; padding: clamp(0.8rem, 2vw, 1.5rem);\n                      font-size: clamp(0.8rem, 2vw, 1.1rem); backdrop-filter: blur(12px); text-align: center;\n                      border: 1px solid rgba(255,255,255,0.15); }\n        .status-log h3 { font-size: clamp(1rem, 2.8vw, 1.4rem); color: #60a5fa; margin-bottom: 0.5rem; }\n        .heartbeat-line { position: absolute; height: 3px; background: linear-gradient(90deg, transparent, #10b981, transparent);\n                           border-radius: 2px; opacity: 0; transform-origin: left center; z-index: 5; }\n        @keyframes heartbeat { 0% { transform: scaleX(0); opacity: 0; } 30% { opacity: 1; } 100% { transform: scaleX(1); opacity: 0; } }\n    <\/style>\n<\/head>\n<body>\n    <div class=\"container\">\n        <p class=\"subtitle\">mTLS \u2022 Full Election \u2022 Heartbeats \u2022 Replication \u2022 Failover &#038; Recovery<\/p>\n\n        <div class=\"graph-container\" id=\"graph\">\n            <div id=\"n1\" class=\"node candidate\"><div class=\"tls-badge\">TLS<\/div><svg width=\"36\" height=\"36\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><rect x=\"2\" y=\"2\" width=\"20\" height=\"8\" rx=\"2\"\/><rect x=\"2\" y=\"14\" width=\"20\" height=\"8\" rx=\"2\"\/><line x1=\"6\" x2=\"6\" y1=\"6\" y2=\"6\"\/><line x1=\"6\" x2=\"6\" y1=\"18\" y2=\"18\"\/><\/svg><div class=\"node-id\">Node 1<\/div><div class=\"node-role\">Candidate<\/div><div class=\"term\">Term: 1<\/div><div class=\"messages\">Messages: 0<\/div><\/div>\n            <div id=\"n2\" class=\"node candidate\"><div class=\"tls-badge\">TLS<\/div><svg width=\"36\" height=\"36\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><rect x=\"2\" y=\"2\" width=\"20\" height=\"8\" rx=\"2\"\/><rect x=\"2\" y=\"14\" width=\"20\" height=\"8\" rx=\"2\"\/><line x1=\"6\" x2=\"6\" y1=\"6\" y2=\"6\"\/><line x1=\"6\" x2=\"6\" y1=\"18\" y2=\"18\"\/><\/svg><div class=\"node-id\">Node 2<\/div><div class=\"node-role\">Candidate<\/div><div class=\"term\">Term: 1<\/div><div class=\"messages\">Messages: 0<\/div><\/div>\n            <div id=\"n3\" class=\"node candidate\"><div class=\"tls-badge\">TLS<\/div><svg width=\"36\" height=\"36\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><rect x=\"2\" y=\"2\" width=\"20\" height=\"8\" rx=\"2\"\/><rect x=\"2\" y=\"14\" width=\"20\" height=\"8\" rx=\"2\"\/><line x1=\"6\" x2=\"6\" y1=\"6\" y2=\"6\"\/><line x1=\"6\" x2=\"6\" y1=\"18\" y2=\"18\"\/><\/svg><div class=\"node-id\">Node 3<\/div><div class=\"node-role\">Candidate<\/div><div class=\"term\">Term: 1<\/div><div class=\"messages\">Messages: 0<\/div><\/div>\n        <\/div>\n\n        <button class=\"action-button\" id=\"start\">Start Animation<\/button>\n\n        <div class=\"status-log\">\n            <h3>Raft Status<\/h3>\n            <p id=\"log\">Click to run the complete secure Raft Animation<\/p>\n        <\/div>\n    <\/div>\n\n    <script>\n        const n = {1: document.getElementById('n1'), 2: document.getElementById('n2'), 3: document.getElementById('n3')};\n        const g = document.getElementById('graph');\n        const log = document.getElementById('log');\n        const btn = document.getElementById('start');\n        let hb;\n\n        function reset() {\n            document.querySelectorAll('.msg, .heartbeat-line').forEach(e => e.remove());\n            clearInterval(hb);\n            [1,2,3].forEach(i => {\n                n[i].className = 'node candidate';\n                n[i].querySelector('.node-role').textContent = 'Candidate';\n                n[i].querySelector('.term').textContent = 'Term: 1';\n                n[i].querySelector('.messages').innerHTML = 'Messages: 0';\n                n[i].style.opacity = '1';\n                n[i].querySelector('.tls-badge').style.opacity = '0';\n                n[i].querySelector('.tls-badge').style.transform = 'scale(0)';\n            });\n            log.innerHTML = \"Click to run the complete secure Raft Animation\";\n            btn.textContent = \"Start Animation\"; btn.disabled = false;\n        }\n\n        function tls(i) { n[i].querySelector('.tls-badge').style.opacity = '1'; n[i].querySelector('.tls-badge').style.transform = 'scale(1)'; }\n        function role(i, r, t=null) { n[i].className = `node ${r}`; n[i].querySelector('.node-role').textContent = r === 'leader' ? 'Leader' : r === 'candidate' ? 'Candidate' : r === 'down' ? 'Down' : r === 'recovering' ? 'Recovering...' : 'Follower'; if(t) n[i].querySelector('.term').textContent = `Term: ${t}`; }\n        function msgc(i, c, com=null) { n[i].querySelector('.messages').innerHTML = com !== null ? `Messages: ${c} | Committed: ${com}` : `Messages: ${c}`; }\n\n        function send(f, t, txt, cls = '', delay = 0, dur = 2000) {\n            const el = document.createElement('div'); el.className = `msg ${cls}`; el.textContent = txt;\n            g.appendChild(el);\n            const fr = n[f].getBoundingClientRect(), tr = n[t].getBoundingClientRect(), gr = g.getBoundingClientRect();\n            const sx = fr.left + fr.width\/2 - gr.left;\n            const sy = fr.top + fr.height\/2 - gr.top;\n            const ex = tr.left + tr.width\/2 - gr.left;\n            const ey = tr.top + tr.height\/2 - gr.top;\n            el.style.left = sx + 'px'; el.style.top = sy + 'px';\n            setTimeout(() => {\n                el.style.transition = `all ${dur}ms cubic-bezier(0.4, 0, 0.2, 1)`;\n                el.style.opacity = 1;\n                el.style.left = ex + 'px';\n                el.style.top = ey + 'px';\n                setTimeout(() => { el.style.opacity = 0; setTimeout(() => el.remove(), 600); }, dur);\n            }, delay);\n        }\n\n        function hbStart(l) {\n            clearInterval(hb);\n            const flw = l === 1 ? [2,3] : l === 2 ? [1,3] : [1,2];\n            hb = setInterval(() => {\n                flw.forEach(f => {\n                    const line = document.createElement('div'); line.className = 'heartbeat-line';\n                    const fr = n[l].getBoundingClientRect(), tr = n[f].getBoundingClientRect(), gr = g.getBoundingClientRect();\n                    const a = Math.atan2(tr.top - fr.top, tr.left - fr.left) * 180 \/ Math.PI;\n                    const len = Math.hypot(tr.left - fr.left, tr.top - fr.top);\n                    line.style.width = len + 'px';\n                    line.style.left = (fr.left - gr.left + fr.width\/2) + 'px';\n                    line.style.top = (fr.top - gr.top + fr.height\/2) + 'px';\n                    line.style.transform = `rotate(${a}deg)`;\n                    g.appendChild(line);\n                    line.style.animation = 'heartbeat 1.8s ease-out forwards';\n                    setTimeout(() => line.remove(), 1800);\n                });\n            }, 2400);\n        }\n\n        btn.addEventListener('click', () => {\n            reset(); btn.disabled = true; btn.textContent = \"Running...\";\n            let d = 600; const s = 3800;\n\n            log.innerHTML = \"Establishing mTLS connections...\";\n            [1,2,3].forEach((i,j) => setTimeout(() => tls(i), d + j*500)); d += 3000;\n\n            \/\/ \u0627\u0646\u062a\u062e\u0627\u0628\u0627\u062a \u0627\u0648\u0644\u06cc\u0647 \u06a9\u0627\u0645\u0644 (\u06f6 \u062a\u0627 RequestVote)\n            log.innerHTML = \"All nodes are Candidates \u2014 Election begins (Term 1)\";\n            send(1,2,\"RequestVote\",\"vote\",200,1800);\n            send(1,3,\"RequestVote\",\"vote\",400,1800);\n            send(2,1,\"RequestVote\",\"vote\",300,1800);\n            send(2,3,\"RequestVote\",\"vote\",500,1800);\n            send(3,1,\"RequestVote\",\"vote\",400,1800);\n            send(3,2,\"RequestVote\",\"vote\",600,1800);\n            d += s;\n\n            \/\/ \u0646\u0648\u062f \u06f3 \u0628\u0631\u0646\u062f\u0647 \u0645\u06cc\u0634\u0647\n            setTimeout(() => {\n                log.innerHTML = \"Node 3 wins \u2192 becomes Leader\";\n                send(1,3,\"Vote Granted\",\"vote\",300,1600);\n                send(2,3,\"Vote Granted\",\"vote\",500,1600);\n                role(3,'leader',1); role(1,'follower',1); role(2,'follower',1);\n                hbStart(3);\n            }, d); d += s;\n\n            \/\/ \u067e\u06cc\u0627\u0645 \u0648 \u062a\u06a9\u062b\u06cc\u0631\n            setTimeout(() => { log.innerHTML = \"Message replication in progress\"; msgc(3,1); send(3,1,\"AppendEntries [msg-001]\",\"append\",400,2400); send(3,2,\"AppendEntries [msg-001]\",\"append\",600,2400); }, d); d += s;\n            setTimeout(() => { msgc(1,1,1); msgc(2,1,1); msgc(3,1,1); send(1,3,\"ACK\",\"ack\",300,1400); send(2,3,\"ACK\",\"ack\",500,1400); }, d); d += s*1.3;\n\n            \/\/ \u06a9\u0631\u0634 \u0646\u0648\u062f \u06f3\n            setTimeout(() => { \n                log.innerHTML = \"Leader (Node 3) crashed! No heartbeat...\"; \n                clearInterval(hb); \n                role(3,'down'); \n                n[3].style.opacity=\"0.3\"; \n            }, d); d += s*1.6;\n\n            \/\/ \u0627\u0646\u062a\u062e\u0627\u0628\u0627\u062a \u062c\u062f\u06cc\u062f \u2014 \u06a9\u0627\u0645\u0644 \u0648 \u0648\u0627\u0642\u0639\u06cc (\u06f4 \u062a\u0627 RequestVote)\n            setTimeout(() => { \n                log.innerHTML = \"Timeout! New election starts (Term 2)\";\n                role(1,'candidate',2); \n                role(2,'candidate',2);\n\n                \/\/ \u0646\u0648\u062f \u06f1 \u0648 \u06f2 \u0628\u0631\u0627\u06cc \u062e\u0648\u062f \u0648 \u06cc\u06a9\u062f\u06cc\u06af\u0631 \u0648 \u062d\u062a\u06cc \u0646\u0648\u062f \u062f\u0627\u0648\u0646 \u062f\u0631\u062e\u0648\u0627\u0633\u062a \u0631\u0623\u06cc \u0645\u06cc\u200c\u0641\u0631\u0633\u062a\u0646\n                send(1,2,\"RequestVote (Term 2)\",\"vote\",200,1800);\n                send(1,3,\"RequestVote (Term 2)\",\"vote\",400,1800);  \/\/ \u0628\u0647 \u0646\u0648\u062f \u062f\u0627\u0648\u0646 \u0647\u0645 \u0645\u06cc\u200c\u0641\u0631\u0633\u062a\u0647 (\u0648\u0627\u0642\u0639\u06cc!)\n                send(2,1,\"RequestVote (Term 2)\",\"vote\",300,1800);\n                send(2,3,\"RequestVote (Term 2)\",\"vote\",500,1800);\n            }, d); d += s;\n\n            \/\/ \u0646\u0648\u062f \u06f1 \u0628\u0631\u0646\u062f\u0647 \u0645\u06cc\u0634\u0647\n            setTimeout(() => { \n                log.innerHTML = \"Node 1 receives majority \u2192 becomes new Leader\";\n                send(2,1,\"Vote Granted\",\"vote\",400,1600);\n                \/\/ \u0646\u0648\u062f \u06f3 \u062f\u0627\u0648\u0646 \u0647\u0633\u062a \u2192 \u062c\u0648\u0627\u0628\u06cc \u0646\u0645\u06cc\u200c\u062f\u0647 (\u062f\u0631\u0633\u062a!)\n                role(1,'leader',2); \n                role(2,'follower',2);\n                hbStart(1);\n            }, d); d += s;\n\n            \/\/ \u0628\u0627\u0632\u06cc\u0627\u0628\u06cc \u0646\u0648\u062f \u06f3\n            setTimeout(() => { \n                log.innerHTML = \"Node 3 recovers and rejoins cluster\"; \n                n[3].style.opacity=\"1\"; \n                role(3,'recovering',2);\n                send(1,3,\"Append(catch-up)\",\"append\",600,2600);\n            }, d); d += s*1.3;\n\n            \/\/ \u067e\u0627\u06cc\u0627\u0646\n            setTimeout(() => { \n                role(3,'follower',2); \n                msgc(3,1,1); \n                log.innerHTML = \"Cluster fully restored and consistent!<br><br><strong>CipherMQ:<\/strong> Secure \u2022 Resilient \u2022 Self-Healing\"; \n                btn.disabled=false; \n                btn.textContent=\"Run Again\"; \n            }, d);\n        });\n    <\/script>\n<\/body>\n<\/html>\n","protected":false},"excerpt":{"rendered":"<p>CipherMQ &#8211; Complete Secure Raft Animation mTLS \u2022 Full Election \u2022 Heartbeats \u2022 Replication \u2022 Failover &#038; Recovery TLS Node 1 Candidate Term: 1 Messages: 0 TLS Node 2 Candidate Term: 1 Messages: 0 TLS Node 3 Candidate Term: 1 Messages: 0 Start Animation Raft Status Click to run the complete secure Raft Animation<\/p>\n","protected":false},"author":1,"featured_media":0,"parent":0,"menu_order":0,"comment_status":"closed","ping_status":"closed","template":"","meta":{"footnotes":""},"class_list":["post-77","page","type-page","status-publish","hentry"],"_links":{"self":[{"href":"https:\/\/ciphermq.com\/docs\/index.php\/wp-json\/wp\/v2\/pages\/77","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/ciphermq.com\/docs\/index.php\/wp-json\/wp\/v2\/pages"}],"about":[{"href":"https:\/\/ciphermq.com\/docs\/index.php\/wp-json\/wp\/v2\/types\/page"}],"author":[{"embeddable":true,"href":"https:\/\/ciphermq.com\/docs\/index.php\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/ciphermq.com\/docs\/index.php\/wp-json\/wp\/v2\/comments?post=77"}],"version-history":[{"count":32,"href":"https:\/\/ciphermq.com\/docs\/index.php\/wp-json\/wp\/v2\/pages\/77\/revisions"}],"predecessor-version":[{"id":127,"href":"https:\/\/ciphermq.com\/docs\/index.php\/wp-json\/wp\/v2\/pages\/77\/revisions\/127"}],"wp:attachment":[{"href":"https:\/\/ciphermq.com\/docs\/index.php\/wp-json\/wp\/v2\/media?parent=77"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}