gistfile1.txt
· 28 KiB · Text
Orginalformat
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description" content="RAID Capacity Calculator - calculate storage efficiency for RAID 0, 1, 5, 6, 10 and ZFS configurations">
<meta name="keywords" content="RAID calculator, storage calculator, ZFS calculator, disk array, RAID capacity">
<meta name="author" content="Karel Wintersky & Deepseek">
<!-- Favicon SVG -->
<link rel="icon" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'>
<rect width='100' height='100' fill='%233367D6'/>
<rect x='20' y='30' width='60' height='40' fill='%23fff' rx='5'/>
<rect x='25' y='35' width='50' height='10' fill='%233367D6'/>
<circle cx='40' cy='60' r='5' fill='%233367D6'/>
<circle cx='60' cy='60' r='5' fill='%233367D6'/>
<path d='M30 75 L70 75 L80 85 L20 85 Z' fill='%23fff'/>
</svg>" type="image/svg+xml">
<!-- Open Graph / Facebook -->
<meta property="og:type" content="website">
<meta property="og:url" content="https://blacktower.wintersky.ru/public/raid_calc.html">
<meta property="og:title" content="RAID Capacity Calculator">
<meta property="og:description" content="Calculate storage efficiency for various RAID configurations">
<meta property="og:image" content="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 1200 630'>
<rect width='1200' height='630' fill='%233367D6'/>
<rect x='100' y='150' width='1000' height='330' fill='%23fff' rx='20'/>
<rect x='150' y='200' width='900' height='80' fill='%233367D6' rx='10'/>
<text x='150' y='300' font-family='Arial' font-size='60' fill='%23000'>RAID 0: <tspan fill='%233367D6'>4×1TB = 4TB</tspan></text>
<text x='150' y='380' font-family='Arial' font-size='60' fill='%23000'>RAID 5: <tspan fill='%233367D6'>4×1TB = 3TB</tspan></text>
<text x='150' y='460' font-family='Arial' font-size='60' fill='%23000'>RAID 10: <tspan fill='%233367D6'>4×1TB = 2TB</tspan></text>
<text x='600' y='550' font-family='Arial' font-size='40' fill='%23fff'>RAID Capacity Calculator</text>
</svg>">
<meta property="og:image:width" content="1200">
<meta property="og:image:height" content="630">
<!-- Twitter -->
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:title" content="RAID Capacity Calculator">
<meta name="twitter:description" content="Professional tool for RAID storage calculations">
<meta name="twitter:image" content="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 1200 630'>
<rect width='1200' height='630' fill='%233367D6'/>
<rect x='100' y='150' width='1000' height='330' fill='%23fff' rx='20'/>
<rect x='150' y='200' width='900' height='80' fill='%233367D6' rx='10'/>
<text x='150' y='300' font-family='Arial' font-size='60' fill='%23000'>RAID 0: <tspan fill='%233367D6'>4×1TB = 4TB</tspan></text>
<text x='150' y='380' font-family='Arial' font-size='60' fill='%23000'>RAID 5: <tspan fill='%233367D6'>4×1TB = 3TB</tspan></text>
<text x='150' y='460' font-family='Arial' font-size='60' fill='%23000'>RAID 10: <tspan fill='%233367D6'>4×1TB = 2TB</tspan></text>
<text x='600' y='550' font-family='Arial' font-size='40' fill='%23fff'>RAID Capacity Calculator</text>
</svg>">
<title>RAID Capacity Calculator</title>
<style>
body {
font-family: Arial, sans-serif;
max-width: 1000px;
margin: 0 auto;
padding: 20px;
line-height: 1.6;
}
.language-switcher {
text-align: right;
margin-bottom: 20px;
}
.language-switcher button {
background: none;
border: 1px solid #ccc;
padding: 5px 10px;
cursor: pointer;
margin-left: 5px;
}
.language-switcher button.active {
background: #eee;
}
.calculator-container {
display: flex;
gap: 20px;
margin-bottom: 30px;
}
.input-section {
flex: 1;
padding: 15px;
border: 1px solid #ddd;
border-radius: 5px;
}
.results-section {
flex: 1;
padding: 15px;
border: 1px solid #ddd;
border-radius: 5px;
background-color: #f5f5f5;
}
.input-group {
margin-bottom: 15px;
}
label {
display: block;
margin-bottom: 5px;
font-weight: bold;
}
input, select {
padding: 8px;
width: 100%;
box-sizing: border-box;
}
.result-item {
margin-bottom: 10px;
padding: 10px;
background-color: white;
border-radius: 3px;
}
.result-label {
font-weight: bold;
color: #555;
}
.hint-section {
padding: 20px;
border: 1px solid #ddd;
border-radius: 5px;
background-color: #f9f9f9;
margin-top: 20px;
}
.hint-title {
font-weight: bold;
margin-bottom: 10px;
color: #333;
}
.hint-content {
line-height: 1.5;
}
/* */
.language-switcher {
text-align: right;
margin-bottom: 20px;
}
.language-switcher button {
background: none;
border: 1px solid #ccc;
padding: 5px 10px;
cursor: pointer;
margin-left: 5px;
display: inline-flex;
align-items: center;
gap: 5px;
}
.language-switcher button:hover {
background-color: #f0f0f0;
}
.language-switcher button.active {
background: #eee;
font-weight: bold;
}
.language-switcher svg {
border: 1px solid #ddd;
}
</style>
</head>
<body>
<!--
<div class="language-switcher">
<button id="lang-en">English</button>
<button id="lang-ru">Русский</button>
</div>
-->
<div class="language-switcher">
<button id="lang-en" title="English">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 60 30" width="20" height="10">
<clipPath id="s">
<path d="M0,0 v30 h60 v-30 z"/>
</clipPath>
<clipPath id="t">
<path d="M30,15 h30 v15 z v15 h-30 z h-30 v-15 z v-15 h30 z"/>
</clipPath>
<g clip-path="url(#s)">
<path d="M0,0 v30 h60 v-30 z" fill="#012169"/>
<path d="M0,0 L60,30 M60,0 L0,30" stroke="#fff" stroke-width="6"/>
<path d="M0,0 L60,30 M60,0 L0,30" stroke="#C8102E" stroke-width="4"/>
<path d="M30,0 v30 M0,15 h60" stroke="#fff" stroke-width="10"/>
<path d="M30,0 v30 M0,15 h60" stroke="#C8102E" stroke-width="6"/>
</g>
<g clip-path="url(#t)">
<path d="M0,0 v30 h60 v-30 z" fill="#012169"/>
<path d="M0,0 L60,30 M60,0 L0,30" stroke="#fff" stroke-width="6"/>
<path d="M0,0 L60,30 M60,0 L0,30" stroke="#C8102E" stroke-width="4"/>
<path d="M30,0 v30 M0,15 h60" stroke="#fff" stroke-width="10"/>
<path d="M30,0 v30 M0,15 h60" stroke="#C8102E" stroke-width="6"/>
</g>
</svg>
<span>English</span>
</button>
<button id="lang-ru" title="Русский">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 60 30" width="20" height="10">
<clipPath id="r">
<path d="M0,0 v30 h60 v-30 z"/>
</clipPath>
<g clip-path="url(#r)">
<path d="M0,0 h60 v10 h-60 z" fill="#fff"/>
<path d="M0,10 h60 v10 h-60 z" fill="#0039A6"/>
<path d="M0,20 h60 v10 h-60 z" fill="#D52B1E"/>
</g>
</svg>
<span>Русский</span>
</button>
</div>
<h1 id="title">RAID Capacity Calculator</h1>
<div class="calculator-container">
<div class="input-section">
<div class="input-group">
<label for="disk-count" id="disk-count-label">Number of disks:</label>
<input type="number" id="disk-count" min="1" value="4">
</div>
<div class="input-group">
<label for="disk-size" id="disk-size-label">Disk size (GB):</label>
<input type="number" id="disk-size" min="1" value="1000">
</div>
<div class="input-group">
<label for="raid-type" id="raid-type-label">RAID type:</label>
<select id="raid-type">
<optgroup label="Standard RAID">
<option value="raid0">RAID 0</option>
<option value="raid1">RAID 1</option>
<option value="raid3">RAID 3</option>
<option value="raid5">RAID 5</option>
<option value="raid6">RAID 6</option>
<option value="raid10">RAID 10</option>
</optgroup>
<optgroup label="ZFS">
<option value="zfsStripe">Stripe set</option>
<option value="zfsMirror">Mirror</option>
<option value="zfsZ1">RAID-Z1</option>
<option value="zfsZ2">RAID-Z2</option>
<option value="zfsZ3">RAID-Z3</option>
</optgroup>
</select>
</div>
</div>
<div class="results-section">
<h2 id="results-title">Results</h2>
<div class="result-item">
<div class="result-label" id="total-space-label">Total space:</div>
<div id="total-space">0</div>
</div>
<div class="result-item">
<div class="result-label" id="effective-space-label">Effective space:</div>
<div id="effective-space">0</div>
</div>
<div class="result-item">
<div class="result-label" id="efficiency-label">Efficiency:</div>
<div id="efficiency">0</div>
</div>
<div class="result-item">
<div class="result-label" id="fault-tolerance-label">Fault tolerance:</div>
<div id="fault-tolerance">None</div>
</div>
</div>
</div>
<div class="hint-section">
<div class="hint-title" id="hint-title">About selected RAID type:</div>
<div class="hint-content" id="hint-content"></div>
</div>
<script>
// Language data
const translations = {
en: {
title: "RAID Capacity Calculator",
diskCountLabel: "Number of disks:",
diskSizeLabel: "Disk size (GB):",
raidTypeLabel: "RAID type:",
resultsTitle: "Results",
totalSpaceLabel: "Total space:",
effectiveSpaceLabel: "Effective space:",
efficiencyLabel: "Efficiency:",
faultToleranceLabel: "Fault tolerance:",
hintTitle: "About selected RAID type:",
raidTypes: {
standard: "Standard RAID",
zfs: "ZFS",
raid0: "RAID 0",
raid1: "RAID 1",
raid3: "RAID 3",
raid5: "RAID 5",
raid6: "RAID 6",
raid10: "RAID 10",
zfsStripe: "Stripe set",
zfsMirror: "Mirror",
zfsZ1: "RAID-Z1",
zfsZ2: "RAID-Z2",
zfsZ3: "RAID-Z3"
},
faultTolerance: {
none: "None",
one: "1 disk failure",
two: "2 disk failures",
three: "3 disk failures",
half: "Up to half of disks (in mirrored pairs)"
},
hints: {
raid0: "RAID 0 (striping) combines multiple disks into one logical unit with data distributed across all disks. Provides improved performance but no redundancy - failure of any disk results in total data loss.",
raid1: "RAID 1 (mirroring) creates an exact copy of data on two or more disks. Provides fault tolerance but at the cost of reduced storage capacity.",
raid3: "RAID 3 uses byte-level striping with a dedicated parity disk. Good for sequential data access but poor for random access. Can tolerate one disk failure.",
raid5: "RAID 5 uses block-level striping with distributed parity. Provides good balance between performance, capacity and fault tolerance. Can tolerate one disk failure.",
raid6: "RAID 6 is similar to RAID 5 but with double distributed parity. Can tolerate two disk failures, making it more reliable for large arrays.",
raid10: "RAID 10 (1+0) combines mirroring and striping. Requires even number of disks (minimum 4). Provides good performance and can tolerate multiple disk failures (one per mirrored pair).",
zfsStripe: "ZFS stripe is similar to RAID 0 - no redundancy but maximum capacity and performance. Data is distributed across all disks in the pool.",
zfsMirror: "ZFS mirror is similar to RAID 1 - data is duplicated on two or more disks. Provides redundancy at the cost of storage capacity.",
zfsZ1: "RAID-Z1 is similar to RAID 5 - single parity with distributed striping. Can tolerate one disk failure while providing good capacity and performance.",
zfsZ2: "RAID-Z2 is similar to RAID 6 - double parity with distributed striping. Can tolerate two disk failures, recommended for larger pools.",
zfsZ3: "RAID-Z3 provides triple parity protection, able to withstand three disk failures. Recommended for very large pools where rebuild times are long."
}
},
ru: {
title: "Калькулятор ёмкости RAID",
diskCountLabel: "Число дисков:",
diskSizeLabel: "Размер диска (ГБ):",
raidTypeLabel: "Тип RAID:",
resultsTitle: "Результаты",
totalSpaceLabel: "Общий объем:",
effectiveSpaceLabel: "Эффективный объем:",
efficiencyLabel: "Эффективность использования:",
faultToleranceLabel: "Отказоустойчивость:",
hintTitle: "О выбранном типе RAID:",
raidTypes: {
standard: "Обычный RAID",
zfs: "ZFS",
raid0: "RAID 0",
raid1: "RAID 1",
raid3: "RAID 3",
raid5: "RAID 5",
raid6: "RAID 6",
raid10: "RAID 10",
zfsStripe: "Stripe set",
zfsMirror: "Mirror",
zfsZ1: "RAID-Z1",
zfsZ2: "RAID-Z2",
zfsZ3: "RAID-Z3"
},
faultTolerance: {
none: "Нет",
one: "1 отказ диска",
two: "2 отказа диска",
three: "3 отказа диска",
half: "До половины дисков (в зеркальных парах)"
},
hints: {
raid0: "RAID 0 (чередование) объединяет несколько дисков в один логический блок с распределением данных по всем дискам. Обеспечивает повышенную производительность, но не имеет избыточности - отказ любого диска приводит к полной потере данных.",
raid1: "RAID 1 (зеркалирование) создает точную копию данных на двух или более дисках. Обеспечивает отказоустойчивость, но за счет уменьшения полезного объема хранилища.",
raid3: "RAID 3 использует чередование на уровне байтов с выделенным диском четности. Хорошо подходит для последовательного доступа к данным, но плохо для случайного. Может выдержать отказ одного диска.",
raid5: "RAID 5 использует чередование на уровне блоков с распределенной четностью. Обеспечивает хороший баланс между производительностью, емкостью и отказоустойчивостью. Может выдержать отказ одного диска.",
raid6: "RAID 6 аналогичен RAID 5, но с двойной распределенной четностью. Может выдержать отказ двух дисков, что делает его более надежным для больших массивов.",
raid10: "RAID 10 (1+0) сочетает зеркалирование и чередование. Требует четного количества дисков (минимум 4). Обеспечивает хорошую производительность и может выдержать отказ нескольких дисков (по одному в каждой зеркальной паре).",
zfsStripe: "ZFS stripe аналогичен RAID 0 - нет избыточности, но максимальная емкость и производительность. Данные распределяются по всем дискам в пуле.",
zfsMirror: "ZFS mirror аналогичен RAID 1 - данные дублируются на двух или более дисках. Обеспечивает избыточность за счет полезного объема хранилища.",
zfsZ1: "RAID-Z1 аналогичен RAID 5 - одинарная четность с распределенным чередованием. Может выдержать отказ одного диска при хорошей емкости и производительности.",
zfsZ2: "RAID-Z2 аналогичен RAID 6 - двойная четность с распределенным чередованием. Может выдержать отказ двух дисков, рекомендуется для больших пулов.",
zfsZ3: "RAID-Z3 обеспечивает тройную защиту четностью, может выдержать отказ трех дисков. Рекомендуется для очень больших пулов, где время восстановления велико."
}
}
};
// Current language
let currentLang = 'en';
// DOM elements
const elements = {
title: document.getElementById('title'),
diskCountLabel: document.getElementById('disk-count-label'),
diskSizeLabel: document.getElementById('disk-size-label'),
raidTypeLabel: document.getElementById('raid-type-label'),
resultsTitle: document.getElementById('results-title'),
totalSpaceLabel: document.getElementById('total-space-label'),
effectiveSpaceLabel: document.getElementById('effective-space-label'),
efficiencyLabel: document.getElementById('efficiency-label'),
faultToleranceLabel: document.getElementById('fault-tolerance-label'),
hintTitle: document.getElementById('hint-title'),
hintContent: document.getElementById('hint-content'),
raidTypeSelect: document.getElementById('raid-type'),
diskCountInput: document.getElementById('disk-count'),
diskSizeInput: document.getElementById('disk-size'),
raidTypeOptgroups: document.querySelectorAll('#raid-type optgroup'),
raidTypeOptions: document.querySelectorAll('#raid-type option')
};
// Save state to URL hash
function saveStateToHash() {
const params = new URLSearchParams();
params.set('lang', currentLang);
params.set('count', elements.diskCountInput.value);
params.set('size', elements.diskSizeInput.value);
params.set('raid', elements.raidTypeSelect.value);
window.location.hash = params.toString();
}
// Load state from URL hash
function loadStateFromHash() {
if (window.location.hash) {
try {
const hash = window.location.hash.substring(1);
const params = new URLSearchParams(hash);
if (params.has('lang') && translations[params.get('lang')]) {
currentLang = params.get('lang');
}
if (params.has('count')) {
elements.diskCountInput.value = params.get('count');
}
if (params.has('size')) {
elements.diskSizeInput.value = params.get('size');
}
if (params.has('raid')) {
elements.raidTypeSelect.value = params.get('raid');
}
return true;
} catch (e) {
console.error("Error parsing hash:", e);
}
}
return false;
}
// Update UI language
function updateLanguage(lang) {
currentLang = lang;
const t = translations[lang];
// Update labels
elements.title.textContent = t.title;
elements.diskCountLabel.textContent = t.diskCountLabel;
elements.diskSizeLabel.textContent = t.diskSizeLabel;
elements.raidTypeLabel.textContent = t.raidTypeLabel;
elements.resultsTitle.textContent = t.resultsTitle;
elements.totalSpaceLabel.textContent = t.totalSpaceLabel;
elements.effectiveSpaceLabel.textContent = t.effectiveSpaceLabel;
elements.efficiencyLabel.textContent = t.efficiencyLabel;
elements.faultToleranceLabel.textContent = t.faultToleranceLabel;
elements.hintTitle.textContent = t.hintTitle;
// Update RAID type options
elements.raidTypeOptgroups[0].label = t.raidTypes.standard;
elements.raidTypeOptgroups[1].label = t.raidTypes.zfs;
const raidTypeOptions = [
t.raidTypes.raid0, t.raidTypes.raid1, t.raidTypes.raid3,
t.raidTypes.raid5, t.raidTypes.raid6, t.raidTypes.raid10,
t.raidTypes.zfsStripe, t.raidTypes.zfsMirror,
t.raidTypes.zfsZ1, t.raidTypes.zfsZ2, t.raidTypes.zfsZ3
];
elements.raidTypeOptions.forEach((option, index) => {
option.textContent = raidTypeOptions[index];
});
// Recalculate to update fault tolerance text and hint
calculate();
}
// Update hint based on selected RAID type
function updateHint(raidType) {
elements.hintContent.textContent = translations[currentLang].hints[raidType] || '';
}
// Calculate RAID parameters
function calculate() {
const diskCount = parseInt(elements.diskCountInput.value) || 0;
const diskSize = parseInt(elements.diskSizeInput.value) || 0;
const raidType = elements.raidTypeSelect.value;
let totalSpace, effectiveSpace, efficiency, faultTolerance;
const t = translations[currentLang].faultTolerance;
switch(raidType) {
case 'raid0':
case 'zfsStripe':
totalSpace = diskCount * diskSize;
effectiveSpace = totalSpace;
efficiency = 100;
faultTolerance = t.none;
break;
case 'raid1':
case 'zfsMirror':
totalSpace = diskCount * diskSize;
effectiveSpace = diskSize;
efficiency = (100 / diskCount).toFixed(2);
faultTolerance = diskCount > 2 ? t.half : t.one;
break;
case 'raid3':
case 'raid5':
case 'zfsZ1':
totalSpace = diskCount * diskSize;
effectiveSpace = (diskCount - 1) * diskSize;
efficiency = ((diskCount - 1) / diskCount * 100).toFixed(2);
faultTolerance = t.one;
break;
case 'raid6':
case 'zfsZ2':
totalSpace = diskCount * diskSize;
effectiveSpace = (diskCount - 2) * diskSize;
efficiency = ((diskCount - 2) / diskCount * 100).toFixed(2);
faultTolerance = t.two;
break;
case 'zfsZ3':
totalSpace = diskCount * diskSize;
effectiveSpace = (diskCount - 3) * diskSize;
efficiency = ((diskCount - 3) / diskCount * 100).toFixed(2);
faultTolerance = t.three;
break;
case 'raid10':
if (diskCount < 2 || diskCount % 2 !== 0) {
effectiveSpace = 0;
efficiency = 0;
faultTolerance = t.none;
} else {
totalSpace = diskCount * diskSize;
effectiveSpace = (diskCount / 2) * diskSize;
efficiency = 50;
faultTolerance = t.half;
}
break;
default:
totalSpace = effectiveSpace = efficiency = 0;
faultTolerance = t.none;
}
// Update results
document.getElementById('total-space').textContent = totalSpace.toLocaleString() + ' Gb';
document.getElementById('effective-space').textContent = effectiveSpace.toLocaleString() + ' Gb';
document.getElementById('efficiency').textContent = efficiency + ' %';
document.getElementById('fault-tolerance').textContent = faultTolerance;
// Update hint
updateHint(raidType);
// Save state
saveStateToHash();
}
// Event listeners
function setupEventListeners() {
elements.diskCountInput.addEventListener('input', calculate);
elements.diskSizeInput.addEventListener('input', calculate);
elements.raidTypeSelect.addEventListener('change', calculate);
document.getElementById('lang-en').addEventListener('click', () => {
currentLang = 'en';
updateLanguage('en');
saveStateToHash();
});
document.getElementById('lang-ru').addEventListener('click', () => {
currentLang = 'ru';
updateLanguage('ru');
saveStateToHash();
});
}
// Initialize
function init() {
// Try to load state from hash
const stateLoaded = loadStateFromHash();
// Setup event listeners
setupEventListeners();
// Update UI with current or default values
updateLanguage(currentLang);
// If no state was loaded, calculate with defaults
if (!stateLoaded) {
calculate();
}
}
// Start the application
init();
</script>
</body>
</html>
1 | <!DOCTYPE html> |
2 | <html lang="en"> |
3 | <head> |
4 | <meta charset="UTF-8"> |
5 | <meta name="viewport" content="width=device-width, initial-scale=1.0"> |
6 | <meta name="description" content="RAID Capacity Calculator - calculate storage efficiency for RAID 0, 1, 5, 6, 10 and ZFS configurations"> |
7 | <meta name="keywords" content="RAID calculator, storage calculator, ZFS calculator, disk array, RAID capacity"> |
8 | <meta name="author" content="Karel Wintersky & Deepseek"> |
9 | |
10 | <!-- Favicon SVG --> |
11 | <link rel="icon" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'> |
12 | <rect width='100' height='100' fill='%233367D6'/> |
13 | <rect x='20' y='30' width='60' height='40' fill='%23fff' rx='5'/> |
14 | <rect x='25' y='35' width='50' height='10' fill='%233367D6'/> |
15 | <circle cx='40' cy='60' r='5' fill='%233367D6'/> |
16 | <circle cx='60' cy='60' r='5' fill='%233367D6'/> |
17 | <path d='M30 75 L70 75 L80 85 L20 85 Z' fill='%23fff'/> |
18 | </svg>" type="image/svg+xml"> |
19 | |
20 | <!-- Open Graph / Facebook --> |
21 | <meta property="og:type" content="website"> |
22 | <meta property="og:url" content="https://blacktower.wintersky.ru/public/raid_calc.html"> |
23 | <meta property="og:title" content="RAID Capacity Calculator"> |
24 | <meta property="og:description" content="Calculate storage efficiency for various RAID configurations"> |
25 | <meta property="og:image" content="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 1200 630'> |
26 | <rect width='1200' height='630' fill='%233367D6'/> |
27 | <rect x='100' y='150' width='1000' height='330' fill='%23fff' rx='20'/> |
28 | <rect x='150' y='200' width='900' height='80' fill='%233367D6' rx='10'/> |
29 | <text x='150' y='300' font-family='Arial' font-size='60' fill='%23000'>RAID 0: <tspan fill='%233367D6'>4×1TB = 4TB</tspan></text> |
30 | <text x='150' y='380' font-family='Arial' font-size='60' fill='%23000'>RAID 5: <tspan fill='%233367D6'>4×1TB = 3TB</tspan></text> |
31 | <text x='150' y='460' font-family='Arial' font-size='60' fill='%23000'>RAID 10: <tspan fill='%233367D6'>4×1TB = 2TB</tspan></text> |
32 | <text x='600' y='550' font-family='Arial' font-size='40' fill='%23fff'>RAID Capacity Calculator</text> |
33 | </svg>"> |
34 | <meta property="og:image:width" content="1200"> |
35 | <meta property="og:image:height" content="630"> |
36 | |
37 | <!-- Twitter --> |
38 | <meta name="twitter:card" content="summary_large_image"> |
39 | <meta name="twitter:title" content="RAID Capacity Calculator"> |
40 | <meta name="twitter:description" content="Professional tool for RAID storage calculations"> |
41 | <meta name="twitter:image" content="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 1200 630'> |
42 | <rect width='1200' height='630' fill='%233367D6'/> |
43 | <rect x='100' y='150' width='1000' height='330' fill='%23fff' rx='20'/> |
44 | <rect x='150' y='200' width='900' height='80' fill='%233367D6' rx='10'/> |
45 | <text x='150' y='300' font-family='Arial' font-size='60' fill='%23000'>RAID 0: <tspan fill='%233367D6'>4×1TB = 4TB</tspan></text> |
46 | <text x='150' y='380' font-family='Arial' font-size='60' fill='%23000'>RAID 5: <tspan fill='%233367D6'>4×1TB = 3TB</tspan></text> |
47 | <text x='150' y='460' font-family='Arial' font-size='60' fill='%23000'>RAID 10: <tspan fill='%233367D6'>4×1TB = 2TB</tspan></text> |
48 | <text x='600' y='550' font-family='Arial' font-size='40' fill='%23fff'>RAID Capacity Calculator</text> |
49 | </svg>"> |
50 | |
51 | <title>RAID Capacity Calculator</title> |
52 | <style> |
53 | body { |
54 | font-family: Arial, sans-serif; |
55 | max-width: 1000px; |
56 | margin: 0 auto; |
57 | padding: 20px; |
58 | line-height: 1.6; |
59 | } |
60 | .language-switcher { |
61 | text-align: right; |
62 | margin-bottom: 20px; |
63 | } |
64 | .language-switcher button { |
65 | background: none; |
66 | border: 1px solid #ccc; |
67 | padding: 5px 10px; |
68 | cursor: pointer; |
69 | margin-left: 5px; |
70 | } |
71 | .language-switcher button.active { |
72 | background: #eee; |
73 | } |
74 | .calculator-container { |
75 | display: flex; |
76 | gap: 20px; |
77 | margin-bottom: 30px; |
78 | } |
79 | .input-section { |
80 | flex: 1; |
81 | padding: 15px; |
82 | border: 1px solid #ddd; |
83 | border-radius: 5px; |
84 | } |
85 | .results-section { |
86 | flex: 1; |
87 | padding: 15px; |
88 | border: 1px solid #ddd; |
89 | border-radius: 5px; |
90 | background-color: #f5f5f5; |
91 | } |
92 | .input-group { |
93 | margin-bottom: 15px; |
94 | } |
95 | label { |
96 | display: block; |
97 | margin-bottom: 5px; |
98 | font-weight: bold; |
99 | } |
100 | input, select { |
101 | padding: 8px; |
102 | width: 100%; |
103 | box-sizing: border-box; |
104 | } |
105 | .result-item { |
106 | margin-bottom: 10px; |
107 | padding: 10px; |
108 | background-color: white; |
109 | border-radius: 3px; |
110 | } |
111 | .result-label { |
112 | font-weight: bold; |
113 | color: #555; |
114 | } |
115 | .hint-section { |
116 | padding: 20px; |
117 | border: 1px solid #ddd; |
118 | border-radius: 5px; |
119 | background-color: #f9f9f9; |
120 | margin-top: 20px; |
121 | } |
122 | .hint-title { |
123 | font-weight: bold; |
124 | margin-bottom: 10px; |
125 | color: #333; |
126 | } |
127 | .hint-content { |
128 | line-height: 1.5; |
129 | } |
130 | |
131 | /* */ |
132 | .language-switcher { |
133 | text-align: right; |
134 | margin-bottom: 20px; |
135 | } |
136 | .language-switcher button { |
137 | background: none; |
138 | border: 1px solid #ccc; |
139 | padding: 5px 10px; |
140 | cursor: pointer; |
141 | margin-left: 5px; |
142 | display: inline-flex; |
143 | align-items: center; |
144 | gap: 5px; |
145 | } |
146 | .language-switcher button:hover { |
147 | background-color: #f0f0f0; |
148 | } |
149 | .language-switcher button.active { |
150 | background: #eee; |
151 | font-weight: bold; |
152 | } |
153 | .language-switcher svg { |
154 | border: 1px solid #ddd; |
155 | } |
156 | |
157 | </style> |
158 | </head> |
159 | <body> |
160 | <!-- |
161 | <div class="language-switcher"> |
162 | <button id="lang-en">English</button> |
163 | <button id="lang-ru">Русский</button> |
164 | </div> |
165 | --> |
166 | <div class="language-switcher"> |
167 | <button id="lang-en" title="English"> |
168 | <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 60 30" width="20" height="10"> |
169 | <clipPath id="s"> |
170 | <path d="M0,0 v30 h60 v-30 z"/> |
171 | </clipPath> |
172 | <clipPath id="t"> |
173 | <path d="M30,15 h30 v15 z v15 h-30 z h-30 v-15 z v-15 h30 z"/> |
174 | </clipPath> |
175 | <g clip-path="url(#s)"> |
176 | <path d="M0,0 v30 h60 v-30 z" fill="#012169"/> |
177 | <path d="M0,0 L60,30 M60,0 L0,30" stroke="#fff" stroke-width="6"/> |
178 | <path d="M0,0 L60,30 M60,0 L0,30" stroke="#C8102E" stroke-width="4"/> |
179 | <path d="M30,0 v30 M0,15 h60" stroke="#fff" stroke-width="10"/> |
180 | <path d="M30,0 v30 M0,15 h60" stroke="#C8102E" stroke-width="6"/> |
181 | </g> |
182 | <g clip-path="url(#t)"> |
183 | <path d="M0,0 v30 h60 v-30 z" fill="#012169"/> |
184 | <path d="M0,0 L60,30 M60,0 L0,30" stroke="#fff" stroke-width="6"/> |
185 | <path d="M0,0 L60,30 M60,0 L0,30" stroke="#C8102E" stroke-width="4"/> |
186 | <path d="M30,0 v30 M0,15 h60" stroke="#fff" stroke-width="10"/> |
187 | <path d="M30,0 v30 M0,15 h60" stroke="#C8102E" stroke-width="6"/> |
188 | </g> |
189 | </svg> |
190 | <span>English</span> |
191 | </button> |
192 | <button id="lang-ru" title="Русский"> |
193 | <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 60 30" width="20" height="10"> |
194 | <clipPath id="r"> |
195 | <path d="M0,0 v30 h60 v-30 z"/> |
196 | </clipPath> |
197 | <g clip-path="url(#r)"> |
198 | <path d="M0,0 h60 v10 h-60 z" fill="#fff"/> |
199 | <path d="M0,10 h60 v10 h-60 z" fill="#0039A6"/> |
200 | <path d="M0,20 h60 v10 h-60 z" fill="#D52B1E"/> |
201 | </g> |
202 | </svg> |
203 | <span>Русский</span> |
204 | </button> |
205 | </div> |
206 | |
207 | <h1 id="title">RAID Capacity Calculator</h1> |
208 | |
209 | <div class="calculator-container"> |
210 | <div class="input-section"> |
211 | <div class="input-group"> |
212 | <label for="disk-count" id="disk-count-label">Number of disks:</label> |
213 | <input type="number" id="disk-count" min="1" value="4"> |
214 | </div> |
215 | |
216 | <div class="input-group"> |
217 | <label for="disk-size" id="disk-size-label">Disk size (GB):</label> |
218 | <input type="number" id="disk-size" min="1" value="1000"> |
219 | </div> |
220 | |
221 | <div class="input-group"> |
222 | <label for="raid-type" id="raid-type-label">RAID type:</label> |
223 | <select id="raid-type"> |
224 | <optgroup label="Standard RAID"> |
225 | <option value="raid0">RAID 0</option> |
226 | <option value="raid1">RAID 1</option> |
227 | <option value="raid3">RAID 3</option> |
228 | <option value="raid5">RAID 5</option> |
229 | <option value="raid6">RAID 6</option> |
230 | <option value="raid10">RAID 10</option> |
231 | </optgroup> |
232 | <optgroup label="ZFS"> |
233 | <option value="zfsStripe">Stripe set</option> |
234 | <option value="zfsMirror">Mirror</option> |
235 | <option value="zfsZ1">RAID-Z1</option> |
236 | <option value="zfsZ2">RAID-Z2</option> |
237 | <option value="zfsZ3">RAID-Z3</option> |
238 | </optgroup> |
239 | </select> |
240 | </div> |
241 | </div> |
242 | |
243 | <div class="results-section"> |
244 | <h2 id="results-title">Results</h2> |
245 | <div class="result-item"> |
246 | <div class="result-label" id="total-space-label">Total space:</div> |
247 | <div id="total-space">0</div> |
248 | </div> |
249 | <div class="result-item"> |
250 | <div class="result-label" id="effective-space-label">Effective space:</div> |
251 | <div id="effective-space">0</div> |
252 | </div> |
253 | <div class="result-item"> |
254 | <div class="result-label" id="efficiency-label">Efficiency:</div> |
255 | <div id="efficiency">0</div> |
256 | </div> |
257 | <div class="result-item"> |
258 | <div class="result-label" id="fault-tolerance-label">Fault tolerance:</div> |
259 | <div id="fault-tolerance">None</div> |
260 | </div> |
261 | </div> |
262 | </div> |
263 | |
264 | <div class="hint-section"> |
265 | <div class="hint-title" id="hint-title">About selected RAID type:</div> |
266 | <div class="hint-content" id="hint-content"></div> |
267 | </div> |
268 | |
269 | <script> |
270 | // Language data |
271 | const translations = { |
272 | en: { |
273 | title: "RAID Capacity Calculator", |
274 | diskCountLabel: "Number of disks:", |
275 | diskSizeLabel: "Disk size (GB):", |
276 | raidTypeLabel: "RAID type:", |
277 | resultsTitle: "Results", |
278 | totalSpaceLabel: "Total space:", |
279 | effectiveSpaceLabel: "Effective space:", |
280 | efficiencyLabel: "Efficiency:", |
281 | faultToleranceLabel: "Fault tolerance:", |
282 | hintTitle: "About selected RAID type:", |
283 | raidTypes: { |
284 | standard: "Standard RAID", |
285 | zfs: "ZFS", |
286 | raid0: "RAID 0", |
287 | raid1: "RAID 1", |
288 | raid3: "RAID 3", |
289 | raid5: "RAID 5", |
290 | raid6: "RAID 6", |
291 | raid10: "RAID 10", |
292 | zfsStripe: "Stripe set", |
293 | zfsMirror: "Mirror", |
294 | zfsZ1: "RAID-Z1", |
295 | zfsZ2: "RAID-Z2", |
296 | zfsZ3: "RAID-Z3" |
297 | }, |
298 | faultTolerance: { |
299 | none: "None", |
300 | one: "1 disk failure", |
301 | two: "2 disk failures", |
302 | three: "3 disk failures", |
303 | half: "Up to half of disks (in mirrored pairs)" |
304 | }, |
305 | hints: { |
306 | raid0: "RAID 0 (striping) combines multiple disks into one logical unit with data distributed across all disks. Provides improved performance but no redundancy - failure of any disk results in total data loss.", |
307 | raid1: "RAID 1 (mirroring) creates an exact copy of data on two or more disks. Provides fault tolerance but at the cost of reduced storage capacity.", |
308 | raid3: "RAID 3 uses byte-level striping with a dedicated parity disk. Good for sequential data access but poor for random access. Can tolerate one disk failure.", |
309 | raid5: "RAID 5 uses block-level striping with distributed parity. Provides good balance between performance, capacity and fault tolerance. Can tolerate one disk failure.", |
310 | raid6: "RAID 6 is similar to RAID 5 but with double distributed parity. Can tolerate two disk failures, making it more reliable for large arrays.", |
311 | raid10: "RAID 10 (1+0) combines mirroring and striping. Requires even number of disks (minimum 4). Provides good performance and can tolerate multiple disk failures (one per mirrored pair).", |
312 | zfsStripe: "ZFS stripe is similar to RAID 0 - no redundancy but maximum capacity and performance. Data is distributed across all disks in the pool.", |
313 | zfsMirror: "ZFS mirror is similar to RAID 1 - data is duplicated on two or more disks. Provides redundancy at the cost of storage capacity.", |
314 | zfsZ1: "RAID-Z1 is similar to RAID 5 - single parity with distributed striping. Can tolerate one disk failure while providing good capacity and performance.", |
315 | zfsZ2: "RAID-Z2 is similar to RAID 6 - double parity with distributed striping. Can tolerate two disk failures, recommended for larger pools.", |
316 | zfsZ3: "RAID-Z3 provides triple parity protection, able to withstand three disk failures. Recommended for very large pools where rebuild times are long." |
317 | } |
318 | }, |
319 | ru: { |
320 | title: "Калькулятор ёмкости RAID", |
321 | diskCountLabel: "Число дисков:", |
322 | diskSizeLabel: "Размер диска (ГБ):", |
323 | raidTypeLabel: "Тип RAID:", |
324 | resultsTitle: "Результаты", |
325 | totalSpaceLabel: "Общий объем:", |
326 | effectiveSpaceLabel: "Эффективный объем:", |
327 | efficiencyLabel: "Эффективность использования:", |
328 | faultToleranceLabel: "Отказоустойчивость:", |
329 | hintTitle: "О выбранном типе RAID:", |
330 | raidTypes: { |
331 | standard: "Обычный RAID", |
332 | zfs: "ZFS", |
333 | raid0: "RAID 0", |
334 | raid1: "RAID 1", |
335 | raid3: "RAID 3", |
336 | raid5: "RAID 5", |
337 | raid6: "RAID 6", |
338 | raid10: "RAID 10", |
339 | zfsStripe: "Stripe set", |
340 | zfsMirror: "Mirror", |
341 | zfsZ1: "RAID-Z1", |
342 | zfsZ2: "RAID-Z2", |
343 | zfsZ3: "RAID-Z3" |
344 | }, |
345 | faultTolerance: { |
346 | none: "Нет", |
347 | one: "1 отказ диска", |
348 | two: "2 отказа диска", |
349 | three: "3 отказа диска", |
350 | half: "До половины дисков (в зеркальных парах)" |
351 | }, |
352 | hints: { |
353 | raid0: "RAID 0 (чередование) объединяет несколько дисков в один логический блок с распределением данных по всем дискам. Обеспечивает повышенную производительность, но не имеет избыточности - отказ любого диска приводит к полной потере данных.", |
354 | raid1: "RAID 1 (зеркалирование) создает точную копию данных на двух или более дисках. Обеспечивает отказоустойчивость, но за счет уменьшения полезного объема хранилища.", |
355 | raid3: "RAID 3 использует чередование на уровне байтов с выделенным диском четности. Хорошо подходит для последовательного доступа к данным, но плохо для случайного. Может выдержать отказ одного диска.", |
356 | raid5: "RAID 5 использует чередование на уровне блоков с распределенной четностью. Обеспечивает хороший баланс между производительностью, емкостью и отказоустойчивостью. Может выдержать отказ одного диска.", |
357 | raid6: "RAID 6 аналогичен RAID 5, но с двойной распределенной четностью. Может выдержать отказ двух дисков, что делает его более надежным для больших массивов.", |
358 | raid10: "RAID 10 (1+0) сочетает зеркалирование и чередование. Требует четного количества дисков (минимум 4). Обеспечивает хорошую производительность и может выдержать отказ нескольких дисков (по одному в каждой зеркальной паре).", |
359 | zfsStripe: "ZFS stripe аналогичен RAID 0 - нет избыточности, но максимальная емкость и производительность. Данные распределяются по всем дискам в пуле.", |
360 | zfsMirror: "ZFS mirror аналогичен RAID 1 - данные дублируются на двух или более дисках. Обеспечивает избыточность за счет полезного объема хранилища.", |
361 | zfsZ1: "RAID-Z1 аналогичен RAID 5 - одинарная четность с распределенным чередованием. Может выдержать отказ одного диска при хорошей емкости и производительности.", |
362 | zfsZ2: "RAID-Z2 аналогичен RAID 6 - двойная четность с распределенным чередованием. Может выдержать отказ двух дисков, рекомендуется для больших пулов.", |
363 | zfsZ3: "RAID-Z3 обеспечивает тройную защиту четностью, может выдержать отказ трех дисков. Рекомендуется для очень больших пулов, где время восстановления велико." |
364 | } |
365 | } |
366 | }; |
367 | |
368 | // Current language |
369 | let currentLang = 'en'; |
370 | |
371 | // DOM elements |
372 | const elements = { |
373 | title: document.getElementById('title'), |
374 | diskCountLabel: document.getElementById('disk-count-label'), |
375 | diskSizeLabel: document.getElementById('disk-size-label'), |
376 | raidTypeLabel: document.getElementById('raid-type-label'), |
377 | resultsTitle: document.getElementById('results-title'), |
378 | totalSpaceLabel: document.getElementById('total-space-label'), |
379 | effectiveSpaceLabel: document.getElementById('effective-space-label'), |
380 | efficiencyLabel: document.getElementById('efficiency-label'), |
381 | faultToleranceLabel: document.getElementById('fault-tolerance-label'), |
382 | hintTitle: document.getElementById('hint-title'), |
383 | hintContent: document.getElementById('hint-content'), |
384 | raidTypeSelect: document.getElementById('raid-type'), |
385 | diskCountInput: document.getElementById('disk-count'), |
386 | diskSizeInput: document.getElementById('disk-size'), |
387 | raidTypeOptgroups: document.querySelectorAll('#raid-type optgroup'), |
388 | raidTypeOptions: document.querySelectorAll('#raid-type option') |
389 | }; |
390 | |
391 | // Save state to URL hash |
392 | function saveStateToHash() { |
393 | const params = new URLSearchParams(); |
394 | params.set('lang', currentLang); |
395 | params.set('count', elements.diskCountInput.value); |
396 | params.set('size', elements.diskSizeInput.value); |
397 | params.set('raid', elements.raidTypeSelect.value); |
398 | |
399 | window.location.hash = params.toString(); |
400 | } |
401 | |
402 | |
403 | // Load state from URL hash |
404 | function loadStateFromHash() { |
405 | if (window.location.hash) { |
406 | try { |
407 | const hash = window.location.hash.substring(1); |
408 | const params = new URLSearchParams(hash); |
409 | |
410 | if (params.has('lang') && translations[params.get('lang')]) { |
411 | currentLang = params.get('lang'); |
412 | } |
413 | if (params.has('count')) { |
414 | elements.diskCountInput.value = params.get('count'); |
415 | } |
416 | if (params.has('size')) { |
417 | elements.diskSizeInput.value = params.get('size'); |
418 | } |
419 | if (params.has('raid')) { |
420 | elements.raidTypeSelect.value = params.get('raid'); |
421 | } |
422 | |
423 | return true; |
424 | } catch (e) { |
425 | console.error("Error parsing hash:", e); |
426 | } |
427 | } |
428 | return false; |
429 | } |
430 | |
431 | // Update UI language |
432 | function updateLanguage(lang) { |
433 | currentLang = lang; |
434 | const t = translations[lang]; |
435 | |
436 | // Update labels |
437 | elements.title.textContent = t.title; |
438 | elements.diskCountLabel.textContent = t.diskCountLabel; |
439 | elements.diskSizeLabel.textContent = t.diskSizeLabel; |
440 | elements.raidTypeLabel.textContent = t.raidTypeLabel; |
441 | elements.resultsTitle.textContent = t.resultsTitle; |
442 | elements.totalSpaceLabel.textContent = t.totalSpaceLabel; |
443 | elements.effectiveSpaceLabel.textContent = t.effectiveSpaceLabel; |
444 | elements.efficiencyLabel.textContent = t.efficiencyLabel; |
445 | elements.faultToleranceLabel.textContent = t.faultToleranceLabel; |
446 | elements.hintTitle.textContent = t.hintTitle; |
447 | |
448 | // Update RAID type options |
449 | elements.raidTypeOptgroups[0].label = t.raidTypes.standard; |
450 | elements.raidTypeOptgroups[1].label = t.raidTypes.zfs; |
451 | |
452 | const raidTypeOptions = [ |
453 | t.raidTypes.raid0, t.raidTypes.raid1, t.raidTypes.raid3, |
454 | t.raidTypes.raid5, t.raidTypes.raid6, t.raidTypes.raid10, |
455 | t.raidTypes.zfsStripe, t.raidTypes.zfsMirror, |
456 | t.raidTypes.zfsZ1, t.raidTypes.zfsZ2, t.raidTypes.zfsZ3 |
457 | ]; |
458 | |
459 | elements.raidTypeOptions.forEach((option, index) => { |
460 | option.textContent = raidTypeOptions[index]; |
461 | }); |
462 | |
463 | // Recalculate to update fault tolerance text and hint |
464 | calculate(); |
465 | } |
466 | |
467 | // Update hint based on selected RAID type |
468 | function updateHint(raidType) { |
469 | elements.hintContent.textContent = translations[currentLang].hints[raidType] || ''; |
470 | } |
471 | |
472 | // Calculate RAID parameters |
473 | function calculate() { |
474 | const diskCount = parseInt(elements.diskCountInput.value) || 0; |
475 | const diskSize = parseInt(elements.diskSizeInput.value) || 0; |
476 | const raidType = elements.raidTypeSelect.value; |
477 | |
478 | let totalSpace, effectiveSpace, efficiency, faultTolerance; |
479 | const t = translations[currentLang].faultTolerance; |
480 | |
481 | switch(raidType) { |
482 | case 'raid0': |
483 | case 'zfsStripe': |
484 | totalSpace = diskCount * diskSize; |
485 | effectiveSpace = totalSpace; |
486 | efficiency = 100; |
487 | faultTolerance = t.none; |
488 | break; |
489 | |
490 | case 'raid1': |
491 | case 'zfsMirror': |
492 | totalSpace = diskCount * diskSize; |
493 | effectiveSpace = diskSize; |
494 | efficiency = (100 / diskCount).toFixed(2); |
495 | faultTolerance = diskCount > 2 ? t.half : t.one; |
496 | break; |
497 | |
498 | case 'raid3': |
499 | case 'raid5': |
500 | case 'zfsZ1': |
501 | totalSpace = diskCount * diskSize; |
502 | effectiveSpace = (diskCount - 1) * diskSize; |
503 | efficiency = ((diskCount - 1) / diskCount * 100).toFixed(2); |
504 | faultTolerance = t.one; |
505 | break; |
506 | |
507 | case 'raid6': |
508 | case 'zfsZ2': |
509 | totalSpace = diskCount * diskSize; |
510 | effectiveSpace = (diskCount - 2) * diskSize; |
511 | efficiency = ((diskCount - 2) / diskCount * 100).toFixed(2); |
512 | faultTolerance = t.two; |
513 | break; |
514 | |
515 | case 'zfsZ3': |
516 | totalSpace = diskCount * diskSize; |
517 | effectiveSpace = (diskCount - 3) * diskSize; |
518 | efficiency = ((diskCount - 3) / diskCount * 100).toFixed(2); |
519 | faultTolerance = t.three; |
520 | break; |
521 | |
522 | case 'raid10': |
523 | if (diskCount < 2 || diskCount % 2 !== 0) { |
524 | effectiveSpace = 0; |
525 | efficiency = 0; |
526 | faultTolerance = t.none; |
527 | } else { |
528 | totalSpace = diskCount * diskSize; |
529 | effectiveSpace = (diskCount / 2) * diskSize; |
530 | efficiency = 50; |
531 | faultTolerance = t.half; |
532 | } |
533 | break; |
534 | |
535 | default: |
536 | totalSpace = effectiveSpace = efficiency = 0; |
537 | faultTolerance = t.none; |
538 | } |
539 | |
540 | // Update results |
541 | document.getElementById('total-space').textContent = totalSpace.toLocaleString() + ' Gb'; |
542 | document.getElementById('effective-space').textContent = effectiveSpace.toLocaleString() + ' Gb'; |
543 | document.getElementById('efficiency').textContent = efficiency + ' %'; |
544 | document.getElementById('fault-tolerance').textContent = faultTolerance; |
545 | |
546 | // Update hint |
547 | updateHint(raidType); |
548 | |
549 | // Save state |
550 | saveStateToHash(); |
551 | } |
552 | |
553 | // Event listeners |
554 | function setupEventListeners() { |
555 | elements.diskCountInput.addEventListener('input', calculate); |
556 | elements.diskSizeInput.addEventListener('input', calculate); |
557 | elements.raidTypeSelect.addEventListener('change', calculate); |
558 | |
559 | document.getElementById('lang-en').addEventListener('click', () => { |
560 | currentLang = 'en'; |
561 | updateLanguage('en'); |
562 | saveStateToHash(); |
563 | }); |
564 | document.getElementById('lang-ru').addEventListener('click', () => { |
565 | currentLang = 'ru'; |
566 | updateLanguage('ru'); |
567 | saveStateToHash(); |
568 | }); |
569 | } |
570 | |
571 | // Initialize |
572 | function init() { |
573 | // Try to load state from hash |
574 | const stateLoaded = loadStateFromHash(); |
575 | |
576 | // Setup event listeners |
577 | setupEventListeners(); |
578 | |
579 | // Update UI with current or default values |
580 | updateLanguage(currentLang); |
581 | |
582 | // If no state was loaded, calculate with defaults |
583 | if (!stateLoaded) { |
584 | calculate(); |
585 | } |
586 | } |
587 | |
588 | // Start the application |
589 | init(); |
590 | </script> |
591 | </body> |
592 | </html> |