fix(qfs): project ui, config naming, sync timestamps - v1.5.4

This commit is contained in:
Mikhail Chusavitin
2026-03-16 08:32:15 +03:00
parent c599897142
commit 35c5600b36
16 changed files with 815 additions and 93 deletions

View File

@@ -203,6 +203,8 @@ let projectsCache = [];
let projectNameByUUID = {};
let projectCodeByUUID = {};
let projectVariantByUUID = {};
let configProjectUUIDByUUID = {};
let configNameByUUID = {};
let pendingMoveConfigUUID = '';
let pendingMoveProjectCode = '';
let pendingCreateConfigName = '';
@@ -343,6 +345,45 @@ function findProjectByInput(input) {
return null;
}
async function resolveUniqueConfigName(baseName, projectUUID, excludeUUID) {
const cleanedBase = (baseName || '').trim();
if (!cleanedBase) {
return {error: 'Введите название'};
}
let configs = [];
if (projectUUID) {
const resp = await fetch('/api/projects/' + projectUUID + '/configs?status=all');
if (!resp.ok) {
return {error: 'Не удалось проверить конфигурации проекта'};
}
const data = await resp.json().catch(() => ({}));
configs = Array.isArray(data.configurations) ? data.configurations : [];
} else {
configs = Object.keys(configProjectUUIDByUUID)
.filter(uuid => !configProjectUUIDByUUID[uuid])
.map(uuid => ({uuid: uuid, name: configNameByUUID[uuid] || ''}));
}
const used = new Set(
configs
.filter(cfg => !excludeUUID || cfg.uuid !== excludeUUID)
.map(cfg => (cfg.name || '').trim().toLowerCase())
);
if (!used.has(cleanedBase.toLowerCase())) {
return {name: cleanedBase, changed: false};
}
let candidate = cleanedBase + '_копия';
let suffix = 2;
while (used.has(candidate.toLowerCase())) {
candidate = cleanedBase + '_копия' + suffix;
suffix++;
}
return {name: candidate, changed: true};
}
function escapeHtml(text) {
const div = document.createElement('div');
div.textContent = text;
@@ -385,14 +426,23 @@ function closeRenameModal() {
async function renameConfig() {
const uuid = document.getElementById('rename-uuid').value;
const name = document.getElementById('rename-input').value.trim();
const rawName = document.getElementById('rename-input').value.trim();
if (!name) {
if (!rawName) {
alert('Введите название');
return;
}
try {
const result = await resolveUniqueConfigName(rawName, configProjectUUIDByUUID[uuid] || '', uuid);
if (result.error) {
alert(result.error);
return;
}
const name = result.name;
if (result.changed) {
document.getElementById('rename-input').value = name;
}
const resp = await fetch('/api/configs/' + uuid + '/rename', {
method: 'PATCH',
headers: {
@@ -416,7 +466,7 @@ async function renameConfig() {
function openCloneModal(uuid, currentName) {
document.getElementById('clone-uuid').value = uuid;
document.getElementById('clone-input').value = currentName + ' (копия)';
document.getElementById('clone-input').value = currentName + '_копия';
document.getElementById('clone-modal').classList.remove('hidden');
document.getElementById('clone-modal').classList.add('flex');
document.getElementById('clone-input').focus();
@@ -430,14 +480,23 @@ function closeCloneModal() {
async function cloneConfig() {
const uuid = document.getElementById('clone-uuid').value;
const name = document.getElementById('clone-input').value.trim();
const rawName = document.getElementById('clone-input').value.trim();
if (!name) {
if (!rawName) {
alert('Введите название');
return;
}
try {
const result = await resolveUniqueConfigName(rawName, configProjectUUIDByUUID[uuid] || '', uuid);
if (result.error) {
alert(result.error);
return;
}
const name = result.name;
if (result.changed) {
document.getElementById('clone-input').value = name;
}
const resp = await fetch('/api/configs/' + uuid + '/clone', {
method: 'POST',
headers: {
@@ -851,6 +910,12 @@ async function loadConfigs() {
}
const data = await resp.json();
configProjectUUIDByUUID = {};
configNameByUUID = {};
(data.configurations || []).forEach(cfg => {
configProjectUUIDByUUID[cfg.uuid] = cfg.project_uuid || '';
configNameByUUID[cfg.uuid] = cfg.name || '';
});
renderConfigs(data.configurations || []);
updatePagination(data.total);
} catch(e) {