feat: add branches feature, security audit, and multi-branch support
This commit is contained in:
+79
-23
@@ -36,25 +36,40 @@
|
||||
</div>
|
||||
|
||||
<div class="modal-overlay" id="editModal" onclick="if(event.target===this)closeEdit()">
|
||||
<div class="edit-modal">
|
||||
<div class="edit-modal" style="max-width:480px">
|
||||
<h3>Редактирование записи</h3>
|
||||
<div>
|
||||
<label>Фамилия и имя</label>
|
||||
<input type="text" id="editName" list="studentEditList">
|
||||
<datalist id="studentEditList"></datalist>
|
||||
</div>
|
||||
<div>
|
||||
<label>Группа</label>
|
||||
<select id="editGroup"></select>
|
||||
</div>
|
||||
<div>
|
||||
<label>Что делала(а)</label>
|
||||
<textarea id="editDesc"></textarea>
|
||||
</div>
|
||||
<div class="actions">
|
||||
<button class="cancel" onclick="closeEdit()">Отмена</button>
|
||||
<button class="save" onclick="saveEdit()">Сохранить</button>
|
||||
</div>
|
||||
<form id="editForm" class="settings-stack" style="gap:12px">
|
||||
<div class="settings-row">
|
||||
<div class="settings-field" style="flex:1">
|
||||
<label>Фамилия и имя <span style="color:#ef4444">*</span></label>
|
||||
<input type="text" id="editName" class="settings-input" list="studentEditList" required autocomplete="off">
|
||||
<datalist id="studentEditList"></datalist>
|
||||
</div>
|
||||
</div>
|
||||
<div class="settings-row">
|
||||
<div class="settings-field" style="flex:1">
|
||||
<label>Группа <span style="color:#ef4444">*</span></label>
|
||||
<select id="editGroup" class="settings-input" required></select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="settings-row">
|
||||
<div class="settings-field" style="flex:1">
|
||||
<label>Что делала(а) <span style="color:#ef4444">*</span></label>
|
||||
<textarea id="editDesc" class="settings-input" required></textarea>
|
||||
</div>
|
||||
</div>
|
||||
<div class="settings-row">
|
||||
<div class="settings-field" style="flex:1">
|
||||
<label>Прикрепить файлы</label>
|
||||
<input type="file" id="editFiles" multiple accept=".pdf,.doc,.docx,.txt,.md,.html,.htm,.zip,.rar,.7z,.jpg,.jpeg,.png,.gif,.webp">
|
||||
<div class="files-preview" id="editFilesPreview"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-foot" style="justify-content:flex-end;gap:8px">
|
||||
<button type="button" class="btn-primary ghost" onclick="closeEdit()">Отмена</button>
|
||||
<button type="submit" class="btn-primary">Сохранить</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -297,6 +312,8 @@ async function openEdit(id) {
|
||||
|
||||
function closeEdit() {
|
||||
document.getElementById('editModal').classList.remove('open');
|
||||
document.getElementById('editFiles').value = '';
|
||||
document.getElementById('editFilesPreview').innerHTML = '';
|
||||
editId = null;
|
||||
}
|
||||
|
||||
@@ -311,11 +328,27 @@ async function saveEdit() {
|
||||
headers: hdrJson(),
|
||||
body: JSON.stringify({ student_name: name, group_id, description })
|
||||
});
|
||||
if (res.ok) {
|
||||
closeEdit();
|
||||
loadEntries();
|
||||
showToast('Запись обновлена');
|
||||
} else { const err = await res.json(); alert(err.error); }
|
||||
if (!res.ok) { const err = await res.json(); alert(err.error); return; }
|
||||
|
||||
const files = document.getElementById('editFiles').files;
|
||||
if (files.length) {
|
||||
const formData = new FormData();
|
||||
for (const f of files) formData.append('files', f);
|
||||
const uploadRes = await fetch(`${API}/api/entries/${editId}/files`, {
|
||||
method: 'POST',
|
||||
headers: { 'X-Admin-Token': token },
|
||||
body: formData
|
||||
});
|
||||
if (!uploadRes.ok) {
|
||||
const err = await uploadRes.json().catch(() => ({}));
|
||||
alert(err.error || 'Ошибка загрузки файлов');
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
closeEdit();
|
||||
loadEntries();
|
||||
showToast('Запись обновлена');
|
||||
}
|
||||
|
||||
async function delEntry(id) {
|
||||
@@ -333,6 +366,29 @@ document.getElementById('searchInput').addEventListener('input', () => {
|
||||
clearTimeout(searchTimer);
|
||||
searchTimer = setTimeout(() => { page = 1; loadEntries(); }, 300);
|
||||
});
|
||||
document.getElementById('editForm').addEventListener('submit', (e) => {
|
||||
e.preventDefault();
|
||||
saveEdit();
|
||||
});
|
||||
document.getElementById('editFiles').addEventListener('change', (e) => {
|
||||
const preview = document.getElementById('editFilesPreview');
|
||||
preview.innerHTML = '';
|
||||
Array.from(e.target.files).forEach((f, i) => {
|
||||
const div = document.createElement('div');
|
||||
div.className = 'file-preview-item';
|
||||
div.style.cssText = 'display:flex;align-items:center;gap:8px;padding:6px 8px;background:var(--bg);border:1px solid var(--border);border-radius:6px;font-size:.8rem';
|
||||
div.innerHTML = `<span>${esc(f.name)}</span> <span style="color:var(--muted)">${(f.size/1024).toFixed(1)} KB</span> <button type="button" onclick="removeEditFile(${i})" style="background:none;border:none;color:var(--muted);cursor:pointer;font-size:1.1rem;line-height:1">✕</button>`;
|
||||
preview.appendChild(div);
|
||||
});
|
||||
});
|
||||
|
||||
function removeEditFile(index) {
|
||||
const input = document.getElementById('editFiles');
|
||||
const dt = new DataTransfer();
|
||||
Array.from(input.files).forEach((f, i) => { if (i !== index) dt.items.add(f); });
|
||||
input.files = dt.files;
|
||||
input.dispatchEvent(new Event('change'));
|
||||
}
|
||||
|
||||
async function exportCSV() {
|
||||
const p = new URLSearchParams();
|
||||
|
||||
Reference in New Issue
Block a user