534 lines
23 KiB
HTML
534 lines
23 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="ru">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<title>Шаблон запроса на резервное копирование</title>
|
||
<style>
|
||
:root {
|
||
--primary: #1a56db;
|
||
--bg: #f8fafc;
|
||
--border: #d1d5db;
|
||
--radius: 8px;
|
||
}
|
||
* { box-sizing: border-box; margin: 0; padding: 0; }
|
||
body {
|
||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Arial, sans-serif;
|
||
background: var(--bg);
|
||
padding: 30px;
|
||
color: #1f2937;
|
||
}
|
||
.container { max-width: 1000px; margin: 0 auto; }
|
||
h1 {
|
||
font-size: 22px; margin-bottom: 4px; color: #111827;
|
||
}
|
||
.subtitle {
|
||
color: #6b7280; font-size: 13px; margin-bottom: 24px;
|
||
}
|
||
.doc-ref {
|
||
font-size: 12px; color: #9ca3af; margin-bottom: 20px;
|
||
}
|
||
.card {
|
||
background: #fff;
|
||
border: 1px solid var(--border);
|
||
border-radius: var(--radius);
|
||
padding: 20px 24px;
|
||
margin-bottom: 16px;
|
||
}
|
||
.card h2 {
|
||
font-size: 15px; font-weight: 600; margin-bottom: 14px;
|
||
color: var(--primary); text-transform: uppercase; letter-spacing: 0.5px;
|
||
}
|
||
.row { display: flex; gap: 16px; flex-wrap: wrap; margin-bottom: 12px; }
|
||
.field { flex: 1; min-width: 200px; }
|
||
.field label {
|
||
display: block; font-size: 12px; font-weight: 500;
|
||
color: #374151; margin-bottom: 4px;
|
||
}
|
||
.field input, .field select, .field textarea {
|
||
width: 100%; padding: 8px 10px; font-size: 14px;
|
||
border: 1px solid var(--border); border-radius: 6px;
|
||
background: #fff; transition: border-color 0.2s;
|
||
}
|
||
.field input:focus, .field select:focus { outline: none; border-color: var(--primary); }
|
||
.field input[readonly] { background: #f3f4f6; color: #4b5563; cursor: default; }
|
||
.field textarea { resize: vertical; min-height: 60px; font-family: monospace; font-size: 13px; }
|
||
.field .hint {
|
||
font-size: 11px; color: #9ca3af; margin-top: 3px;
|
||
}
|
||
.auto-badge {
|
||
display: inline-block; font-size: 10px; background: #dbeafe; color: var(--primary);
|
||
padding: 1px 6px; border-radius: 4px; margin-left: 6px; font-weight: 500;
|
||
}
|
||
.params-grid {
|
||
display: grid; grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
|
||
gap: 10px; margin-top: 8px;
|
||
}
|
||
.param-card {
|
||
background: #f0f4ff; border-radius: 6px; padding: 10px 14px;
|
||
border-left: 3px solid var(--primary);
|
||
}
|
||
.param-card .label { font-size: 11px; color: #6b7280; text-transform: uppercase; }
|
||
.param-card .value { font-size: 16px; font-weight: 700; color: var(--primary); margin-top: 2px; }
|
||
.param-card .value.green { color: #059669; }
|
||
.param-card .value.orange { color: #d97706; }
|
||
.param-card .value.red { color: #dc2626; }
|
||
.gen-name {
|
||
font-family: 'Courier New', monospace; font-size: 13px;
|
||
background: #1f2937; color: #e5e7eb; padding: 10px 14px;
|
||
border-radius: 6px; word-break: break-all; margin-top: 6px;
|
||
white-space: pre-wrap;
|
||
}
|
||
.btn-group { display: flex; gap: 10px; margin-top: 20px; }
|
||
.btn {
|
||
padding: 10px 24px; border: none; border-radius: 6px;
|
||
font-size: 14px; font-weight: 500; cursor: pointer; transition: 0.2s;
|
||
}
|
||
.btn-primary { background: var(--primary); color: #fff; }
|
||
.btn-primary:hover { background: #1d4ed8; }
|
||
.btn-secondary { background: #e5e7eb; color: #374151; }
|
||
.btn-secondary:hover { background: #d1d5db; }
|
||
.btn-danger { background: #fee2e2; color: #dc2626; }
|
||
.btn-danger:hover { background: #fecaca; }
|
||
.output-section { margin-top: 12px; }
|
||
.output-section textarea { min-height: 120px; font-family: monospace; font-size: 13px; }
|
||
.warning {
|
||
background: #fffbeb; border: 1px solid #fde68a; border-radius: 6px;
|
||
padding: 10px 14px; font-size: 13px; color: #92400e; margin-top: 8px;
|
||
}
|
||
@media print {
|
||
body { background: #fff; padding: 0; }
|
||
.btn-group, .doc-ref { display: none; }
|
||
.card { border: 1px solid #ddd; break-inside: avoid; }
|
||
}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<div class="container">
|
||
<h1>Шаблон запроса на резервное копирование ИТ-системы</h1>
|
||
<p class="subtitle">М НСП-НТ-03-02 26-09-2025 «Методика резервного копирования ИТ-систем»</p>
|
||
<p class="doc-ref">Номер заявки: <span id="reqId"></span> | Дата: <span id="reqDate"></span></p>
|
||
|
||
<!-- 1. Общая информация -->
|
||
<div class="card">
|
||
<h2>1. Общая информация об ИТ-системе</h2>
|
||
<div class="row">
|
||
<div class="field">
|
||
<label>Наименование ИТ-системы</label>
|
||
<input type="text" id="sysName" placeholder="Введите наименование">
|
||
</div>
|
||
<div class="field">
|
||
<label>Идентификатор в EATool</label>
|
||
<input type="text" id="eaToolId" placeholder="IS000XXXX" maxlength="10">
|
||
<div class="hint">Формат: IS000XXXX. Для систем без ID — IS0000000</div>
|
||
</div>
|
||
</div>
|
||
<div class="row">
|
||
<div class="field">
|
||
<label>Ответственный за эксплуатацию ИТ-системы</label>
|
||
<input type="text" id="responsible" placeholder="ФИО / должность">
|
||
</div>
|
||
<div class="field">
|
||
<label>Подразделение</label>
|
||
<input type="text" id="department" placeholder="Наименование подразделения">
|
||
</div>
|
||
</div>
|
||
<div class="row">
|
||
<div class="field">
|
||
<label>Класс критичности ИТ-системы</label>
|
||
<select id="criticality" onchange="autoFill()">
|
||
<option value="">— Выберите класс критичности —</option>
|
||
<option value="max">Максимальный</option>
|
||
<option value="high">Высокий</option>
|
||
<option value="medium">Средний</option>
|
||
<option value="low">Низкий</option>
|
||
</select>
|
||
</div>
|
||
<div class="field">
|
||
<label>Тип данных</label>
|
||
<select id="dataType" onchange="autoFill()">
|
||
<option value="">— Выберите тип данных —</option>
|
||
<option value="vm">Виртуальная машина</option>
|
||
<option value="fs">Файловая система</option>
|
||
<option value="db">База данных / Приложение</option>
|
||
</select>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 2. Класс резервирования (авто) -->
|
||
<div class="card" id="classCard" style="display:none">
|
||
<h2>2. Класс резервирования и параметры восстановления <span class="auto-badge">авто</span></h2>
|
||
<div class="params-grid" id="paramsGrid"></div>
|
||
</div>
|
||
|
||
<!-- 3. Параметры резервного копирования -->
|
||
<div class="card" id="backupParamsCard" style="display:none">
|
||
<h2>3. Параметры резервного копирования</h2>
|
||
|
||
<div class="row">
|
||
<div class="field">
|
||
<label>Класс резервирования (КР)</label>
|
||
<input type="text" id="krClass" readonly>
|
||
</div>
|
||
<div class="field">
|
||
<label>Периодичность резервного копирования</label>
|
||
<input type="text" id="backupPeriod" readonly>
|
||
</div>
|
||
</div>
|
||
<div class="row">
|
||
<div class="field">
|
||
<label>RTO (целевое время восстановления)</label>
|
||
<input type="text" id="rto" readonly>
|
||
</div>
|
||
<div class="field">
|
||
<label>RPO (целевая точка восстановления)</label>
|
||
<input type="text" id="rpo" readonly>
|
||
</div>
|
||
</div>
|
||
<div class="row">
|
||
<div class="field">
|
||
<label>Срок хранения — ежедневные копии (S)</label>
|
||
<input type="text" id="storeDaily" readonly>
|
||
</div>
|
||
<div class="field">
|
||
<label>Срок хранения — еженедельные копии (F)</label>
|
||
<input type="text" id="storeWeekly" readonly>
|
||
</div>
|
||
<div class="field">
|
||
<label>Срок хранения — ежемесячные копии (G)</label>
|
||
<input type="text" id="storeMonthly" readonly>
|
||
</div>
|
||
</div>
|
||
<div class="row">
|
||
<div class="field">
|
||
<label>Место хранения резервных копий</label>
|
||
<input type="text" id="storageLocation" readonly>
|
||
</div>
|
||
<div class="field">
|
||
<label>Наличие удалённой реплики</label>
|
||
<input type="text" id="hasReplica" readonly>
|
||
</div>
|
||
</div>
|
||
<div class="row" id="dbDetailsRow" style="display:none">
|
||
<div class="field">
|
||
<label>Периодичность копирования транзакционных журналов БД</label>
|
||
<input type="text" id="tlogPeriod" readonly>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="field" style="margin-top:8px">
|
||
<label>Согласованное изменение параметров (если применимо)</label>
|
||
<textarea id="agreedChanges" placeholder="Решение ответственного за эксплуатацию ИТ-системы и ответственного за эксплуатацию СРК (если периодичность/сроки изменены)"></textarea>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 4. Детали объекта -->
|
||
<div class="card" id="detailsCard" style="display:none">
|
||
<h2>4. Детали объекта резервирования</h2>
|
||
<div class="row">
|
||
<div class="field">
|
||
<label>Тип объекта РК</label>
|
||
<select id="rkType">
|
||
<option value="">— Выберите —</option>
|
||
<option value="zVirt">zVirt</option>
|
||
<option value="HyperV">Hyper-V</option>
|
||
<option value="VMware">VMware</option>
|
||
<option value="File">File</option>
|
||
<option value="Block">Block</option>
|
||
<option value="MSSQL">MSSQL</option>
|
||
<option value="PSQL">Postgres</option>
|
||
<option value="Mongo">Mongo</option>
|
||
<option value="DB2">DB2</option>
|
||
<option value="Exchange">Exchange</option>
|
||
<option value="SP">SharePoint</option>
|
||
<option value="Oracle">Oracle</option>
|
||
<option value="SAP">SAP HANA</option>
|
||
<option value="Sybase">Sybase</option>
|
||
<option value="K8s">Kubernetes</option>
|
||
</select>
|
||
</div>
|
||
<div class="field">
|
||
<label>Схема резервного копирования (F-I-D)</label>
|
||
<select id="fidScheme" onchange="toggleF1Warning()">
|
||
<option value="">— Выберите —</option>
|
||
<option value="F">F — только полное</option>
|
||
<option value="F1">F1 — разовое полное</option>
|
||
<option value="I">I — всегда инкрементальное</option>
|
||
<option value="FI">FI — полное + инкрементальное</option>
|
||
<option value="FD">FD — полное + дифференциальное</option>
|
||
<option value="SF">SF — синтетическое полное</option>
|
||
<option value="FDI">FDI — полное + дифференциальное + инкрементальное</option>
|
||
<option value="TLB">TLB — транзакционные логи</option>
|
||
</select>
|
||
</div>
|
||
</div>
|
||
<div id="f1Warning" class="warning" style="display:none">
|
||
Внимание! Для схемы F1 требуется ежеквартальная ревизия актуальности. По итогам ревизии план защиты и соответствующие файлы РК могут быть удалены.
|
||
</div>
|
||
<div class="row">
|
||
<div class="field">
|
||
<label>Имя сервера / ресурса (Hostname/FQDN)</label>
|
||
<input type="text" id="hostname" placeholder="server-name.domain.local">
|
||
<div class="hint">Верхний регистр — Windows, нижний — Unix. Группа серверов: GrSrv-<NAME></div>
|
||
</div>
|
||
<div class="field">
|
||
<label>Объём резервируемых данных (Гб)</label>
|
||
<input type="number" id="volumeGb" placeholder="0" min="0" step="0.1">
|
||
</div>
|
||
</div>
|
||
<div class="row">
|
||
<div class="field">
|
||
<label>Допустимое окно резервного копирования</label>
|
||
<input type="text" id="backupWindow" placeholder="например: 22:00–06:00">
|
||
</div>
|
||
<div class="field">
|
||
<label>Порядковый номер политики (X)</label>
|
||
<input type="text" id="policyNum" placeholder="1, 2, ... (опционально)">
|
||
<div class="hint">При одинаковых названиях, но разных расписаниях</div>
|
||
</div>
|
||
</div>
|
||
<div class="row">
|
||
<div class="field">
|
||
<label>Прогнозируемый объём на конец года (Гб)</label>
|
||
<input type="number" id="volumeMaxGb" placeholder="0" min="0" step="0.1">
|
||
</div>
|
||
<div class="field">
|
||
<label>Коэффициент дедупликации</label>
|
||
<input type="text" id="dedupFactor" placeholder="Например: 2:1, 3:1">
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 5. Сгенерированное наименование плана защиты -->
|
||
<div class="card" id="nameCard" style="display:none">
|
||
<h2>5. Наименование плана защиты / политики РК <span class="auto-badge">авто</span></h2>
|
||
<p style="font-size:12px; color:#6b7280; margin-bottom:6px;">
|
||
Шаблон: <code><IS000XXXX>_<Объект-РК>_<Тип РК>_<F-I-D>_<X></code>
|
||
</p>
|
||
<div class="gen-name" id="generatedName">—</div>
|
||
<div class="field" style="margin-top:8px">
|
||
<label>Примечание (для служебных / тестовых политик)</label>
|
||
<select id="policyNote" onchange="updateGeneratedName()">
|
||
<option value="">— Стандартная политика —</option>
|
||
<option value="Test">Test — тестовая политика</option>
|
||
<option value="System">System — служебная (сбор логов, скрипты) — для КиберБэкап</option>
|
||
</select>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 6. Полный вывод -->
|
||
<div class="card" id="outputCard" style="display:none">
|
||
<h2>6. Спецификация резервного копирования (сводка)</h2>
|
||
<div class="output-section">
|
||
<textarea id="fullOutput" readonly></textarea>
|
||
</div>
|
||
<div class="btn-group">
|
||
<button class="btn btn-primary" onclick="copyOutput()">Копировать спецификацию</button>
|
||
<button class="btn btn-secondary" onclick="window.print()">Печать</button>
|
||
<button class="btn btn-danger" onclick="resetForm()">Сбросить</button>
|
||
</div>
|
||
</div>
|
||
|
||
</div>
|
||
|
||
<script>
|
||
const KR_CONFIG = {
|
||
max: {
|
||
kr: 1, period: '2 ч.', rto: '2 ч.', rpo: '2 ч.',
|
||
storeDaily: '1 нед.', storeWeekly: '1 м.', storeMonthly: '1 г.',
|
||
location: 'ОЦОД + РЦОД + ЛБ', replica: 'Да',
|
||
tlog: '2 ч.', color: 'red'
|
||
},
|
||
high: {
|
||
kr: 2, period: '8 ч.', rto: '8 ч.', rpo: '8 ч.',
|
||
storeDaily: '1 нед.', storeWeekly: '1 м.', storeMonthly: '1 г.',
|
||
location: 'ОЦОД + РЦОД + ЛБ', replica: 'Да',
|
||
tlog: '8 ч.', color: 'orange'
|
||
},
|
||
medium: {
|
||
kr: 3, period: '16 ч.', rto: '16 ч.', rpo: '16 ч.',
|
||
storeDaily: '1 нед.', storeWeekly: '1 м.', storeMonthly: '1 г.',
|
||
location: 'ОЦОД', replica: 'Нет',
|
||
tlog: '16 ч.', color: 'green'
|
||
},
|
||
low: {
|
||
kr: 4, period: '24 ч.', rto: '24 ч.', rpo: '24 ч.',
|
||
storeDaily: '1 нед.', storeWeekly: '1 м.', storeMonthly: '1 г.',
|
||
location: 'ОЦОД', replica: 'Нет',
|
||
tlog: '24 ч.', color: 'green'
|
||
}
|
||
};
|
||
|
||
const CRIT_NAMES = { max: 'Максимальный', high: 'Высокий', medium: 'Средний', low: 'Низкий' };
|
||
const DATA_TYPES = { vm: 'Виртуальная машина', fs: 'Файловая система', db: 'База данных / Приложение' };
|
||
|
||
function get(id) { return document.getElementById(id); }
|
||
|
||
function autoFill() {
|
||
const crit = get('criticality').value;
|
||
const dt = get('dataType').value;
|
||
|
||
if (!crit || !dt) return;
|
||
|
||
const cfg = KR_CONFIG[crit];
|
||
|
||
get('classCard').style.display = 'block';
|
||
get('backupParamsCard').style.display = 'block';
|
||
get('detailsCard').style.display = 'block';
|
||
get('nameCard').style.display = 'block';
|
||
get('outputCard').style.display = 'block';
|
||
|
||
// Параметры восстановления
|
||
const labels = {
|
||
max: { label: 'КР 1 — Максимальный', desc: 'RTO 2ч / RPO 2ч' },
|
||
high: { label: 'КР 2 — Высокий', desc: 'RTO 8ч / RPO 8ч' },
|
||
medium: { label: 'КР 3 — Средний', desc: 'RTO 16ч / RPO 16ч' },
|
||
low: { label: 'КР 4 — Низкий', desc: 'RTO 24ч / RPO 24ч' }
|
||
};
|
||
|
||
get('paramsGrid').innerHTML = `
|
||
<div class="param-card"><div class="label">Класс резервирования</div><div class="value ${cfg.color}">КР ${cfg.kr}</div></div>
|
||
<div class="param-card"><div class="label">Периодичность</div><div class="value">${cfg.period}</div></div>
|
||
<div class="param-card"><div class="label">RTO</div><div class="value">${cfg.rto}</div></div>
|
||
<div class="param-card"><div class="label">RPO</div><div class="value">${cfg.rpo}</div></div>
|
||
<div class="param-card"><div class="label">Хранение (ежедн/еженед/ежемес)</div><div class="value">${cfg.storeDaily} / ${cfg.storeWeekly} / ${cfg.storeMonthly}</div></div>
|
||
<div class="param-card"><div class="label">Место хранения</div><div class="value">${cfg.location}</div></div>
|
||
<div class="param-card"><div class="label">Удалённая реплика</div><div class="value ${cfg.replica === 'Да' ? 'red' : 'green'}">${cfg.replica}</div></div>
|
||
`;
|
||
|
||
get('krClass').value = 'КР ' + cfg.kr;
|
||
get('backupPeriod').value = cfg.period;
|
||
get('rto').value = cfg.rto;
|
||
get('rpo').value = cfg.rpo;
|
||
get('storeDaily').value = cfg.storeDaily;
|
||
get('storeWeekly').value = cfg.storeWeekly;
|
||
get('storeMonthly').value = cfg.storeMonthly;
|
||
get('storageLocation').value = cfg.location;
|
||
get('hasReplica').value = cfg.replica;
|
||
|
||
// Показываем/скрываем строку TLog
|
||
get('dbDetailsRow').style.display = (dt === 'db') ? 'flex' : 'none';
|
||
if (dt === 'db') {
|
||
get('tlogPeriod').value = 'каждые ' + cfg.tlog;
|
||
}
|
||
|
||
updateGeneratedName();
|
||
updateFullOutput();
|
||
}
|
||
|
||
function toggleF1Warning() {
|
||
get('f1Warning').style.display = (get('fidScheme').value === 'F1') ? 'block' : 'none';
|
||
updateGeneratedName();
|
||
}
|
||
|
||
function updateGeneratedName() {
|
||
const eaId = get('eaToolId').value || 'IS0000000';
|
||
const host = get('hostname').value || '<Имя_сервера>';
|
||
const rkType = get('rkType').value || '<Тип_РК>';
|
||
const fid = get('fidScheme').value || '<F-I-D>';
|
||
const num = get('policyNum').value;
|
||
const note = get('policyNote').value;
|
||
|
||
let base = eaId + '_' + host + '_' + rkType + '_' + fid;
|
||
if (num) base += '_' + num;
|
||
if (note) base = note + '_' + base;
|
||
|
||
get('generatedName').textContent = base;
|
||
updateFullOutput();
|
||
}
|
||
|
||
function updateFullOutput() {
|
||
const crit = get('criticality').value;
|
||
const cfg = crit ? KR_CONFIG[crit] : null;
|
||
const dt = get('dataType').value;
|
||
|
||
let o = '=== СПЕЦИФИКАЦИЯ РЕЗЕРВНОГО КОПИРОВАНИЯ ИТ-СИСТЕМЫ ===\n';
|
||
o += 'Методика: М НСП-НТ-03-02 26-09-2025\n\n';
|
||
|
||
o += '1. ИТ-СИСТЕМА\n';
|
||
o += ' Наименование: ' + (get('sysName').value || '(не указано)') + '\n';
|
||
o += ' ID EATool: ' + (get('eaToolId').value || 'IS0000000') + '\n';
|
||
o += ' Ответственный: ' + (get('responsible').value || '(не указано)') + '\n';
|
||
o += ' Подразделение: ' + (get('department').value || '(не указано)') + '\n';
|
||
o += ' Класс критичности: ' + (CRIT_NAMES[crit] || '(не выбран)') + '\n';
|
||
o += ' Тип данных: ' + (DATA_TYPES[dt] || '(не выбран)') + '\n\n';
|
||
|
||
if (cfg) {
|
||
o += '2. КЛАСС РЕЗЕРВИРОВАНИЯ\n';
|
||
o += ' КР: КР ' + cfg.kr + '\n';
|
||
o += ' Периодичность: ' + cfg.period + '\n';
|
||
o += ' RTO: ' + cfg.rto + '\n';
|
||
o += ' RPO: ' + cfg.rpo + '\n\n';
|
||
|
||
o += '3. ХРАНЕНИЕ\n';
|
||
o += ' Ежедневные (S): ' + cfg.storeDaily + '\n';
|
||
o += ' Еженедельные (F): ' + cfg.storeWeekly + '\n';
|
||
o += ' Ежемесячные (G): ' + cfg.storeMonthly + '\n\n';
|
||
|
||
o += '4. РАЗМЕЩЕНИЕ\n';
|
||
o += ' Место хранения: ' + cfg.location + '\n';
|
||
o += ' Удалённая реплика: ' + cfg.replica + '\n';
|
||
if (dt === 'db') {
|
||
o += ' Транзакционные логи: каждые ' + cfg.tlog + '\n';
|
||
}
|
||
o += '\n';
|
||
}
|
||
|
||
o += '5. ОБЪЕКТ РЕЗЕРВИРОВАНИЯ\n';
|
||
o += ' Тип объекта РК: ' + (get('rkType').value || '(не выбран)') + '\n';
|
||
o += ' Схема F-I-D: ' + (get('fidScheme').value || '(не выбрана)') + '\n';
|
||
o += ' Hostname/FQDN: ' + (get('hostname').value || '(не указан)') + '\n';
|
||
o += ' Объём (текущий): ' + (get('volumeGb').value || '0') + ' Гб\n';
|
||
o += ' Объём (прогноз): ' + (get('volumeMaxGb').value || '0') + ' Гб\n';
|
||
o += ' Дедупликация: ' + (get('dedupFactor').value || '(не указан)') + '\n';
|
||
o += ' Окно РК: ' + (get('backupWindow').value || '(не указано)') + '\n\n';
|
||
|
||
o += '6. ПЛАН ЗАЩИТЫ / ПОЛИТИКА РК\n';
|
||
o += ' Наименование: ' + get('generatedName').textContent + '\n\n';
|
||
|
||
if (get('agreedChanges').value.trim()) {
|
||
o += '7. СОГЛАСОВАННЫЕ ИЗМЕНЕНИЯ\n';
|
||
o += ' ' + get('agreedChanges').value + '\n\n';
|
||
}
|
||
|
||
o += '=== КОНЕЦ СПЕЦИФИКАЦИИ ===';
|
||
|
||
get('fullOutput').value = o;
|
||
}
|
||
|
||
// Автообновление сводки при любых изменениях
|
||
document.querySelectorAll('input, select, textarea').forEach(el => {
|
||
el.addEventListener('change', () => { updateGeneratedName(); updateFullOutput(); });
|
||
el.addEventListener('input', () => { updateGeneratedName(); updateFullOutput(); });
|
||
});
|
||
|
||
function copyOutput() {
|
||
const ta = get('fullOutput');
|
||
ta.select();
|
||
document.execCommand('copy');
|
||
const btn = event.target;
|
||
const orig = btn.textContent;
|
||
btn.textContent = 'Скопировано!';
|
||
setTimeout(() => btn.textContent = orig, 2000);
|
||
}
|
||
|
||
function resetForm() {
|
||
if (!confirm('Сбросить все поля?')) return;
|
||
document.querySelectorAll('input, textarea').forEach(el => el.value = '');
|
||
document.querySelectorAll('select').forEach(el => el.selectedIndex = 0);
|
||
['classCard','backupParamsCard','detailsCard','nameCard','outputCard','f1Warning','dbDetailsRow'].forEach(id => {
|
||
get(id).style.display = 'none';
|
||
});
|
||
}
|
||
|
||
// Инициализация
|
||
(function() {
|
||
const now = new Date();
|
||
const pad = n => String(n).padStart(2, '0');
|
||
get('reqDate').textContent = now.getFullYear() + '-' + pad(now.getMonth()+1) + '-' + pad(now.getDate());
|
||
get('reqId').textContent = 'ЗРК-' + now.getFullYear() + pad(now.getMonth()+1) + pad(now.getDate()) + '-' + pad(Math.floor(Math.random()*999)+1);
|
||
})();
|
||
</script>
|
||
</body>
|
||
</html>
|