{"id":41,"date":"2026-03-26T08:22:40","date_gmt":"2026-03-26T08:22:40","guid":{"rendered":"https:\/\/anco.starhorizon.ru\/?page_id=41"},"modified":"2026-03-26T08:26:40","modified_gmt":"2026-03-26T08:26:40","slug":"dashboard","status":"publish","type":"page","link":"https:\/\/anco.starhorizon.ru\/?page_id=41","title":{"rendered":"Dashboard"},"content":{"rendered":"        <div class=\"ss14-grafana-dashboard\" style=\" \">\n            <div class=\"dashboard-header\">\n                <div class=\"header-left\">\n                    <h2 id=\"server-name-header\">StarHorizon Server<\/h2>\n                    <span class=\"server-status\" id=\"server-status\">\n                        <span class=\"status-dot\"><\/span>\n                        <span class=\"status-text\">\u041f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0435...<\/span>\n                    <\/span>\n                <\/div>\n            <\/div>\n\n            <div class=\"stats-grid\">\n                <div class=\"stat-card players-card\">\n                    <div class=\"card-header\"><span class=\"card-icon\"><\/span><span class=\"card-title\">\u0418\u0433\u0440\u043e\u043a\u0438<\/span><\/div>\n                    <div class=\"card-content\">\n                        <div class=\"main-value\" id=\"players-total\">0 \/ 0<\/div>\n                        <div class=\"sub-stats\"><div class=\"sub-stat\"><span class=\"sub-label\">\u041e\u043d\u043b\u0430\u0439\u043d:<\/span><span class=\"sub-value\" id=\"players-online\">0<\/span><\/div><\/div>\n                    <\/div>\n                <\/div>\n\n                <div class=\"stat-card round-card\">\n                    <div class=\"card-header\"><span class=\"card-icon\"><\/span><span class=\"card-title\">\u0420\u0430\u0443\u043d\u0434<\/span><\/div>\n                    <div class=\"card-content\">\n                        <div class=\"main-value\" id=\"round-number\">#0<\/div>\n                        <div class=\"sub-stats\">\n                            <div class=\"sub-stat\"><span class=\"sub-label\">\u0420\u0430\u0443\u043d\u0434:<\/span><span class=\"sub-value status-badge\" id=\"run-level\">-<\/span><\/div>\n                            <div class=\"sub-stat\"><span class=\"sub-label\">\u0414\u043b\u0438\u0442\u0435\u043b\u044c\u043d\u043e\u0441\u0442\u044c:<\/span><span class=\"sub-value\" id=\"round-duration\">00:00:00<\/span><\/div>\n                        <\/div>\n                    <\/div>\n                <\/div>\n\n                <div class=\"stat-card map-card\">\n                    <div class=\"card-header\"><span class=\"card-icon\"><\/span><span class=\"card-title\">\u041a\u0430\u0440\u0442\u0430<\/span><\/div>\n                    <div class=\"card-content\">\n                        <div class=\"main-value map-name\" id=\"map-name\">Unknown<\/div>\n                        <div class=\"sub-stats\">\n                            <div class=\"sub-stat\"><span class=\"sub-label\">\u0420\u0435\u0436\u0438\u043c:<\/span><span class=\"sub-value\" id=\"preset-name\">-<\/span><\/div>\n                            <div class=\"sub-stat\"><span class=\"sub-label\">\u041e\u043f\u0438\u0441\u0430\u043d\u0438\u0435:<\/span><span class=\"sub-value\" id=\"preset-desc\">-<\/span><\/div>\n                        <\/div>\n                    <\/div>\n                <\/div>\n\n                <div class=\"stat-card info-card\">\n                    <div class=\"card-header\"><span class=\"card-icon\"><\/span><span class=\"card-title\">\u0418\u043d\u0444\u043e<\/span><\/div>\n                    <div class=\"card-content\">\n                        <div class=\"info-list\">\n                            <div class=\"info-item\"><span class=\"info-label\">\u041d\u0430\u0447\u0430\u043b\u043e \u0440\u0430\u0443\u043d\u0434\u0430:<\/span><span class=\"info-value\" id=\"info-round-start\">--:--:--<\/span><\/div>\n                            <div class=\"info-item\"><span class=\"info-label\">\u041e\u043a\u043e\u043d\u0447\u0430\u043d\u0438\u0435:<\/span><span class=\"info-value\" id=\"info-round-end\">--:--:--<\/span><\/div>\n                            <div class=\"info-item\"><span class=\"info-label\">\u041e\u043a\u043e\u043d\u0447\u0430\u043d\u0438\u044f \u0440\u0430\u0443\u043d\u0434\u0430 \u0447\u0435\u0440\u0435\u0437:<\/span><span class=\"info-value\" id=\"info-round-remaining\">--:--:--<\/span><\/div>\n                        <\/div>\n                    <\/div>\n                <\/div>\n            <\/div>\n\n            <div class=\"bottom-row\">\n                <div class=\"wide-card\">\n                    <div class=\"card-header\">\n                        <span class=\"card-title\">\u0410\u043a\u0442\u0438\u0432\u043d\u043e\u0441\u0442\u044c \u0438\u0433\u0440\u043e\u043a\u043e\u0432<\/span>\n                        <div class=\"graph-filters\">\n                            <button class=\"filter-btn\" data-hours=\"1\">1 \u0447<\/button>\n                            <button class=\"filter-btn\" data-hours=\"3\">3 \u0447<\/button>\n                            <button class=\"filter-btn active\" data-hours=\"6\">6 \u0447<\/button>\n                            <button class=\"filter-btn\" data-hours=\"12\">12 \u0447<\/button>\n                            <button class=\"filter-btn\" data-hours=\"24\">\u0421\u0443\u0442\u043a\u0438<\/button>\n                        <\/div>\n                    <\/div>\n                    <div class=\"card-body\"><canvas id=\"activityChart\"><\/canvas><\/div>\n                <\/div>\n            <\/div>\n        <\/div>\n\n        <script>\n        var ss14_ajax_url = \"https:\/\/anco.starhorizon.ru\/wp-admin\/admin-ajax.php\";\n        document.addEventListener('DOMContentLoaded', function() {\n            const $ = (sel) => document.querySelector(sel);\n            const $$ = (sel) => document.querySelectorAll(sel);\n            let activityChart, playersHistory = [], roundDuration = 24 * 3600 + 7 * 60;\n\n            function loadHistory(hours = 6) {\n                fetch(ss14_ajax_url + '?action=ss14_get_history&hours=' + hours)\n                    .then(r => r.json())\n                    .then(res => {\n                        if (res.success) {\n                            playersHistory = res.data.map(item => ({ timestamp: item.timestamp * 1000, players: parseInt(item.players) }));\n                            if (playersHistory.length > 300) {\n                                const step = Math.ceil(playersHistory.length \/ 300);\n                                playersHistory = playersHistory.filter((_, i) => i % step === 0);\n                            }\n                            renderChart();\n                        }\n                    }).catch(err => { console.error(err); playersHistory = []; renderChart(); });\n            }\n\n            function renderChart() {\n                const labels = playersHistory.map(h => { const d = new Date(h.timestamp); return String(d.getHours()).padStart(2, '0') + ':' + String(d.getMinutes()).padStart(2, '0'); });\n                activityChart.data.labels = labels;\n                activityChart.data.datasets[0].data = playersHistory.map(h => h.players);\n                activityChart.update('none');\n            }\n\n            $$('.filter-btn').forEach(btn => btn.addEventListener('click', function() {\n                $$('.filter-btn').forEach(b => b.classList.remove('active'));\n                this.classList.add('active');\n                loadHistory(parseFloat(this.dataset.hours));\n            }));\n\n            function initCharts() {\n                const ctx = $('#activityChart');\n                if (ctx && typeof Chart !== 'undefined') {\n                    activityChart = new Chart(ctx, {\n                        type: 'line',\n                        data: { labels: [], datasets: [{ label: '\u0418\u0433\u0440\u043e\u043a\u043e\u0432', data: [], borderColor: '#d21e1e', backgroundColor: 'rgba(210, 30, 30, 0.1)', fill: true, tension: 0.4, pointRadius: 5, pointHoverRadius: 7 }] },\n                        options: { responsive: true, maintainAspectRatio: false, plugins: { legend: { display: false } }, scales: { x: { grid: { color: '#1a1a1a' }, ticks: { color: '#d21e1e', maxTicksLimit: 10 } }, y: { grid: { color: '#1a1a1a' }, ticks: { color: '#d21e1e', stepSize: 1 }, beginAtZero: true } } }\n                    });\n                }\n            }\n\n            function updateDashboard() {\n                fetch(ss14_ajax_url + '?action=ss14_get_stats').then(r => r.json()).then(res => { if (res.success) updateUI(res.data); }).catch(err => console.error(err));\n            }\n\n            function updateUI(data) {\n                const status = $('#server-status');\n                if (status) { \n                    status.classList.remove('status-online', 'status-offline');\n                    status.classList.add(data.status === 'online' ? 'status-online' : 'status-offline');\n                    const st = status.querySelector('.status-text'); \n                    if (st) st.textContent = data.status === 'online' ? '\u041e\u043d\u043b\u0430\u0439\u043d' : '\u041e\u0444\u043b\u0430\u0439\u043d'; \n                }\n                if (data.status !== 'online') return;\n                $('#server-name-header').textContent = data.server_name || 'StarHorizon Server';\n                $('#players-total').textContent = (data.players || 0) + ' \/ ' + (data.max_players || 100);\n                $('#players-online').textContent = data.players || 0;\n                $('#round-number').textContent = '#' + (data.round_id || 0);\n                const rl = $('#run-level'); if (rl) { rl.textContent = data.run_level_name || '\u2014'; rl.className = 'sub-value status-badge ' + (data.run_level === 1 ? 'round-active' : data.run_level === 0 ? 'round-waiting' : 'round-ended'); }\n                const rd = $('#round-duration'); if (rd) { if (data.round_start_time) { const dur = Math.floor(Date.now() \/ 1000) - new Date(data.round_start_time).getTime() \/ 1000; rd.textContent = formatTime(dur); } else rd.textContent = '00:00'; }\n                $('#map-name').textContent = data.map || 'Unknown';\n                $('#preset-name').textContent = data.preset || '\u2014';\n                $('#preset-desc').textContent = data.preset_description || '\u2014';\n                if (data.round_start_time) {\n                    const st = new Date(data.round_start_time);\n                    $('#info-round-start').textContent = String(st.getDate()).padStart(2, '0') + '.' + String(st.getMonth() + 1).padStart(2, '0') + ' ' + String(st.getHours()).padStart(2, '0') + ':' + String(st.getMinutes()).padStart(2, '0');\n                    const et = new Date(st.getTime() + roundDuration * 1000);\n                    $('#info-round-end').textContent = String(et.getDate()).padStart(2, '0') + '.' + String(et.getMonth() + 1).padStart(2, '0') + ' ' + String(et.getHours()).padStart(2, '0') + ':' + String(et.getMinutes()).padStart(2, '0');\n                    const rem = Math.max(0, Math.floor((et - Date.now()) \/ 1000));\n                    $('#info-round-remaining').textContent = formatTime(rem);\n                    \/\/ \u041f\u0443\u043b\u044c\u0441\u0430\u0446\u0438\u044f \u0435\u0441\u043b\u0438 \u043e\u0441\u0442\u0430\u043b\u043e\u0441\u044c \u043c\u0435\u043d\u044c\u0448\u0435 \u0447\u0430\u0441\u0430 (\u0431\u0435\u043b\u044b\u0439 \u0446\u0432\u0435\u0442)\n                    if (rem < 3600) {\n                        $('#info-round-remaining').style.color = '#ffffff';\n                        $('#info-round-remaining').classList.add('urgent-pulse');\n                    } else if (rem < 14400) { \/\/ \u043c\u0435\u043d\u044c\u0448\u0435 4 \u0447\u0430\u0441\u043e\u0432 - \u0436\u0451\u043b\u0442\u044b\u0439\n                        $('#info-round-remaining').style.color = '#f5a623';\n                        $('#info-round-remaining').classList.remove('urgent-pulse');\n                    } else { \/\/ \u0431\u043e\u043b\u044c\u0448\u0435 4 \u0447\u0430\u0441\u043e\u0432 - \u043a\u0440\u0430\u0441\u043d\u044b\u0439 #f05a5a\n                        $('#info-round-remaining').style.color = '#f05a5a';\n                        $('#info-round-remaining').classList.remove('urgent-pulse');\n                    }\n                } else { $('#info-round-start').textContent = '\u2014'; $('#info-round-end').textContent = '\u2014'; $('#info-round-remaining').textContent = '\u2014'; }\n            }\n\n            function formatTime(s) { s = Math.floor(s); const d = Math.floor(s \/ 86400), h = Math.floor((s % 86400) \/ 3600), m = Math.floor((s % 3600) \/ 60); return d > 0 ? d + ' \u0434\u043d ' + String(h).padStart(2, '0') + ':' + String(m).padStart(2, '0') : String(h).padStart(2, '0') + ':' + String(m).padStart(2, '0'); }\n\n            initCharts(); loadHistory(24); updateDashboard(); setInterval(updateDashboard, 5000);\n        });\n        <\/script>\n        \n","protected":false},"excerpt":{"rendered":"","protected":false},"author":2,"featured_media":0,"parent":0,"menu_order":0,"comment_status":"closed","ping_status":"closed","template":"","meta":{"footnotes":""},"class_list":["post-41","page","type-page","status-publish","hentry"],"_links":{"self":[{"href":"https:\/\/anco.starhorizon.ru\/index.php?rest_route=\/wp\/v2\/pages\/41","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/anco.starhorizon.ru\/index.php?rest_route=\/wp\/v2\/pages"}],"about":[{"href":"https:\/\/anco.starhorizon.ru\/index.php?rest_route=\/wp\/v2\/types\/page"}],"author":[{"embeddable":true,"href":"https:\/\/anco.starhorizon.ru\/index.php?rest_route=\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"https:\/\/anco.starhorizon.ru\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=41"}],"version-history":[{"count":2,"href":"https:\/\/anco.starhorizon.ru\/index.php?rest_route=\/wp\/v2\/pages\/41\/revisions"}],"predecessor-version":[{"id":49,"href":"https:\/\/anco.starhorizon.ru\/index.php?rest_route=\/wp\/v2\/pages\/41\/revisions\/49"}],"wp:attachment":[{"href":"https:\/\/anco.starhorizon.ru\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=41"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}