Fix project selection and add project settings UI
This commit is contained in:
@@ -1428,15 +1428,17 @@ func setupRouter(cfg *config.Config, local *localdb.LocalDB, connMgr *db.Connect
|
|||||||
|
|
||||||
// Return simplified list of all projects (UUID + Name only)
|
// Return simplified list of all projects (UUID + Name only)
|
||||||
type ProjectSimple struct {
|
type ProjectSimple struct {
|
||||||
UUID string `json:"uuid"`
|
UUID string `json:"uuid"`
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
|
IsActive bool `json:"is_active"`
|
||||||
}
|
}
|
||||||
|
|
||||||
simplified := make([]ProjectSimple, 0, len(allProjects))
|
simplified := make([]ProjectSimple, 0, len(allProjects))
|
||||||
for _, p := range allProjects {
|
for _, p := range allProjects {
|
||||||
simplified = append(simplified, ProjectSimple{
|
simplified = append(simplified, ProjectSimple{
|
||||||
UUID: p.UUID,
|
UUID: p.UUID,
|
||||||
Name: p.Name,
|
Name: p.Name,
|
||||||
|
IsActive: p.IsActive,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1455,7 +1457,12 @@ func setupRouter(cfg *config.Config, local *localdb.LocalDB, connMgr *db.Connect
|
|||||||
}
|
}
|
||||||
project, err := projectService.Create(dbUsername, &req)
|
project, err := projectService.Create(dbUsername, &req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
switch {
|
||||||
|
case errors.Is(err, services.ErrProjectNameExists):
|
||||||
|
c.JSON(http.StatusConflict, gin.H{"error": err.Error()})
|
||||||
|
default:
|
||||||
|
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
c.JSON(http.StatusCreated, project)
|
c.JSON(http.StatusCreated, project)
|
||||||
@@ -1490,6 +1497,8 @@ func setupRouter(cfg *config.Config, local *localdb.LocalDB, connMgr *db.Connect
|
|||||||
project, err := projectService.Update(c.Param("uuid"), dbUsername, &req)
|
project, err := projectService.Update(c.Param("uuid"), dbUsername, &req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
switch {
|
switch {
|
||||||
|
case errors.Is(err, services.ErrProjectNameExists):
|
||||||
|
c.JSON(http.StatusConflict, gin.H{"error": err.Error()})
|
||||||
case errors.Is(err, services.ErrProjectNotFound):
|
case errors.Is(err, services.ErrProjectNotFound):
|
||||||
c.JSON(http.StatusNotFound, gin.H{"error": err.Error()})
|
c.JSON(http.StatusNotFound, gin.H{"error": err.Error()})
|
||||||
case errors.Is(err, services.ErrProjectForbidden):
|
case errors.Is(err, services.ErrProjectForbidden):
|
||||||
|
|||||||
@@ -147,8 +147,8 @@ func (h *WebHandler) render(c *gin.Context, name string, data gin.H) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (h *WebHandler) Index(c *gin.Context) {
|
func (h *WebHandler) Index(c *gin.Context) {
|
||||||
// Redirect to configs page - configurator is accessed via /configurator?uuid=...
|
// Redirect to projects page - configurator is accessed via /configurator?uuid=...
|
||||||
c.Redirect(302, "/configs")
|
c.Redirect(302, "/projects")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *WebHandler) Configurator(c *gin.Context) {
|
func (h *WebHandler) Configurator(c *gin.Context) {
|
||||||
|
|||||||
@@ -11,8 +11,8 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"git.mchus.pro/mchus/quoteforge/internal/appstate"
|
|
||||||
"git.mchus.pro/mchus/quoteforge/internal/appmeta"
|
"git.mchus.pro/mchus/quoteforge/internal/appmeta"
|
||||||
|
"git.mchus.pro/mchus/quoteforge/internal/appstate"
|
||||||
"github.com/glebarez/sqlite"
|
"github.com/glebarez/sqlite"
|
||||||
mysqlDriver "github.com/go-sql-driver/mysql"
|
mysqlDriver "github.com/go-sql-driver/mysql"
|
||||||
uuidpkg "github.com/google/uuid"
|
uuidpkg "github.com/google/uuid"
|
||||||
@@ -434,18 +434,36 @@ func (l *LocalDB) ListConfigurationsWithFilters(status string, search string, of
|
|||||||
query := l.db.Model(&LocalConfiguration{})
|
query := l.db.Model(&LocalConfiguration{})
|
||||||
switch status {
|
switch status {
|
||||||
case "active":
|
case "active":
|
||||||
query = query.Where("is_active = ?", true)
|
query = query.Where("local_configurations.is_active = ?", true)
|
||||||
case "archived":
|
case "archived":
|
||||||
query = query.Where("is_active = ?", false)
|
query = query.Where("local_configurations.is_active = ?", false)
|
||||||
case "all", "":
|
case "all", "":
|
||||||
// no-op
|
// no-op
|
||||||
default:
|
default:
|
||||||
query = query.Where("is_active = ?", true)
|
query = query.Where("local_configurations.is_active = ?", true)
|
||||||
}
|
}
|
||||||
|
|
||||||
search = strings.TrimSpace(search)
|
search = strings.TrimSpace(search)
|
||||||
if search != "" {
|
if search != "" {
|
||||||
query = query.Where("LOWER(name) LIKE ?", "%"+strings.ToLower(search)+"%")
|
needle := "%" + strings.ToLower(search) + "%"
|
||||||
|
hasProjectsTable := l.db.Migrator().HasTable(&LocalProject{})
|
||||||
|
hasServerModel := l.db.Migrator().HasColumn(&LocalConfiguration{}, "server_model")
|
||||||
|
|
||||||
|
conditions := []string{"LOWER(local_configurations.name) LIKE ?"}
|
||||||
|
args := []interface{}{needle}
|
||||||
|
|
||||||
|
if hasProjectsTable {
|
||||||
|
query = query.Joins("LEFT JOIN local_projects lp ON lp.uuid = local_configurations.project_uuid")
|
||||||
|
conditions = append(conditions, "LOWER(COALESCE(lp.name, '')) LIKE ?")
|
||||||
|
args = append(args, needle)
|
||||||
|
}
|
||||||
|
|
||||||
|
if hasServerModel {
|
||||||
|
conditions = append(conditions, "LOWER(COALESCE(local_configurations.server_model, '')) LIKE ?")
|
||||||
|
args = append(args, needle)
|
||||||
|
}
|
||||||
|
|
||||||
|
query = query.Where(strings.Join(conditions, " OR "), args...)
|
||||||
}
|
}
|
||||||
|
|
||||||
var total int64
|
var total int64
|
||||||
@@ -454,7 +472,7 @@ func (l *LocalDB) ListConfigurationsWithFilters(status string, search string, of
|
|||||||
}
|
}
|
||||||
|
|
||||||
var configs []LocalConfiguration
|
var configs []LocalConfiguration
|
||||||
if err := query.Order("created_at DESC").Offset(offset).Limit(limit).Find(&configs).Error; err != nil {
|
if err := query.Order("local_configurations.created_at DESC").Offset(offset).Limit(limit).Find(&configs).Error; err != nil {
|
||||||
return nil, 0, err
|
return nil, 0, err
|
||||||
}
|
}
|
||||||
return configs, total, nil
|
return configs, total, nil
|
||||||
|
|||||||
@@ -16,8 +16,9 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
ErrProjectNotFound = errors.New("project not found")
|
ErrProjectNotFound = errors.New("project not found")
|
||||||
ErrProjectForbidden = errors.New("access to project forbidden")
|
ErrProjectForbidden = errors.New("access to project forbidden")
|
||||||
|
ErrProjectNameExists = errors.New("project name already exists")
|
||||||
)
|
)
|
||||||
|
|
||||||
type ProjectService struct {
|
type ProjectService struct {
|
||||||
@@ -49,6 +50,9 @@ func (s *ProjectService) Create(ownerUsername string, req *CreateProjectRequest)
|
|||||||
if name == "" {
|
if name == "" {
|
||||||
return nil, fmt.Errorf("project name is required")
|
return nil, fmt.Errorf("project name is required")
|
||||||
}
|
}
|
||||||
|
if err := s.ensureUniqueProjectName("", name); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
now := time.Now()
|
now := time.Now()
|
||||||
localProject := &localdb.LocalProject{
|
localProject := &localdb.LocalProject{
|
||||||
@@ -81,6 +85,9 @@ func (s *ProjectService) Update(projectUUID, ownerUsername string, req *UpdatePr
|
|||||||
if name == "" {
|
if name == "" {
|
||||||
return nil, fmt.Errorf("project name is required")
|
return nil, fmt.Errorf("project name is required")
|
||||||
}
|
}
|
||||||
|
if err := s.ensureUniqueProjectName(projectUUID, name); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
localProject.Name = name
|
localProject.Name = name
|
||||||
if req.TrackerURL != nil {
|
if req.TrackerURL != nil {
|
||||||
@@ -99,6 +106,32 @@ func (s *ProjectService) Update(projectUUID, ownerUsername string, req *UpdatePr
|
|||||||
return localdb.LocalToProject(localProject), nil
|
return localdb.LocalToProject(localProject), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *ProjectService) ensureUniqueProjectName(excludeUUID, name string) error {
|
||||||
|
normalized := normalizeProjectName(name)
|
||||||
|
if normalized == "" {
|
||||||
|
return fmt.Errorf("project name is required")
|
||||||
|
}
|
||||||
|
|
||||||
|
projects, err := s.localDB.GetAllProjects(true)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for i := range projects {
|
||||||
|
project := projects[i]
|
||||||
|
if excludeUUID != "" && project.UUID == excludeUUID {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if normalizeProjectName(project.Name) == normalized {
|
||||||
|
return ErrProjectNameExists
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func normalizeProjectName(name string) string {
|
||||||
|
return strings.ToLower(strings.TrimSpace(name))
|
||||||
|
}
|
||||||
|
|
||||||
func (s *ProjectService) Archive(projectUUID, ownerUsername string) error {
|
func (s *ProjectService) Archive(projectUUID, ownerUsername string) error {
|
||||||
return s.setProjectActive(projectUUID, ownerUsername, false)
|
return s.setProjectActive(projectUUID, ownerUsername, false)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -448,9 +448,13 @@ async function createConfig() {
|
|||||||
let projectUUID = '';
|
let projectUUID = '';
|
||||||
|
|
||||||
if (projectName) {
|
if (projectName) {
|
||||||
const existingProject = projectsCache.find(p => p.is_active && p.name.toLowerCase() === projectName.toLowerCase());
|
const matchedProject = projectsCache.find(p => p.name.toLowerCase() === projectName.toLowerCase());
|
||||||
if (existingProject) {
|
if (matchedProject) {
|
||||||
projectUUID = existingProject.uuid;
|
if (!matchedProject.is_active) {
|
||||||
|
alert('Проект с таким названием находится в архиве. Восстановите его или выберите другой.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
projectUUID = matchedProject.uuid;
|
||||||
} else {
|
} else {
|
||||||
pendingCreateConfigName = name;
|
pendingCreateConfigName = name;
|
||||||
pendingCreateProjectName = projectName;
|
pendingCreateProjectName = projectName;
|
||||||
@@ -529,9 +533,13 @@ async function confirmMoveProject() {
|
|||||||
let projectUUID = '';
|
let projectUUID = '';
|
||||||
|
|
||||||
if (projectName) {
|
if (projectName) {
|
||||||
const existingProject = projectsCache.find(p => p.is_active && p.name.toLowerCase() === projectName.toLowerCase());
|
const matchedProject = projectsCache.find(p => p.name.toLowerCase() === projectName.toLowerCase());
|
||||||
if (existingProject) {
|
if (matchedProject) {
|
||||||
projectUUID = existingProject.uuid;
|
if (!matchedProject.is_active) {
|
||||||
|
alert('Проект с таким названием находится в архиве. Восстановите его или выберите другой.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
projectUUID = matchedProject.uuid;
|
||||||
} else {
|
} else {
|
||||||
pendingMoveConfigUUID = uuid;
|
pendingMoveConfigUUID = uuid;
|
||||||
pendingMoveProjectName = projectName;
|
pendingMoveProjectName = projectName;
|
||||||
@@ -587,6 +595,10 @@ async function confirmCreateProjectOnMove() {
|
|||||||
body: JSON.stringify({ name: projectName })
|
body: JSON.stringify({ name: projectName })
|
||||||
});
|
});
|
||||||
if (!createResp.ok) {
|
if (!createResp.ok) {
|
||||||
|
if (createResp.status === 409) {
|
||||||
|
alert('Проект с таким названием уже существует');
|
||||||
|
return;
|
||||||
|
}
|
||||||
const err = await createResp.json();
|
const err = await createResp.json();
|
||||||
alert('Не удалось создать проект: ' + (err.error || 'ошибка'));
|
alert('Не удалось создать проект: ' + (err.error || 'ошибка'));
|
||||||
return;
|
return;
|
||||||
@@ -623,6 +635,10 @@ async function confirmCreateProjectOnMove() {
|
|||||||
body: JSON.stringify({ name: projectName })
|
body: JSON.stringify({ name: projectName })
|
||||||
});
|
});
|
||||||
if (!createResp.ok) {
|
if (!createResp.ok) {
|
||||||
|
if (createResp.status === 409) {
|
||||||
|
alert('Проект с таким названием уже существует');
|
||||||
|
return;
|
||||||
|
}
|
||||||
const err = await createResp.json();
|
const err = await createResp.json();
|
||||||
alert('Не удалось создать проект: ' + (err.error || 'ошибка'));
|
alert('Не удалось создать проект: ' + (err.error || 'ошибка'));
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -13,13 +13,16 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="action-buttons" class="mt-4 grid grid-cols-1 sm:grid-cols-2 gap-3">
|
<div id="action-buttons" class="mt-4 grid grid-cols-1 sm:grid-cols-3 gap-3">
|
||||||
<button onclick="openCreateModal()" class="py-3 bg-blue-600 text-white rounded-lg hover:bg-blue-700 font-medium">
|
<button onclick="openCreateModal()" class="py-3 bg-blue-600 text-white rounded-lg hover:bg-blue-700 font-medium">
|
||||||
+ Создать новую квоту
|
+ Создать новую квоту
|
||||||
</button>
|
</button>
|
||||||
<button onclick="openImportModal()" class="py-3 bg-indigo-600 text-white rounded-lg hover:bg-indigo-700 font-medium">
|
<button onclick="openImportModal()" class="py-3 bg-indigo-600 text-white rounded-lg hover:bg-indigo-700 font-medium">
|
||||||
Импорт квоты
|
Импорт квоты
|
||||||
</button>
|
</button>
|
||||||
|
<button onclick="openProjectSettingsModal()" class="py-3 bg-gray-700 text-white rounded-lg hover:bg-gray-800 font-medium">
|
||||||
|
Параметры
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="mt-2">
|
<div class="mt-2">
|
||||||
<a id="tracker-link" href="https://tracker.yandex.ru/OPS-1933" target="_blank" rel="noopener noreferrer" class="text-sm text-blue-600 hover:text-blue-800 hover:underline">
|
<a id="tracker-link" href="https://tracker.yandex.ru/OPS-1933" target="_blank" rel="noopener noreferrer" class="text-sm text-blue-600 hover:text-blue-800 hover:underline">
|
||||||
@@ -113,6 +116,29 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div id="project-settings-modal" class="fixed inset-0 bg-black bg-opacity-50 hidden items-center justify-center z-50">
|
||||||
|
<div class="bg-white rounded-lg shadow-xl w-full max-w-md mx-4 p-6">
|
||||||
|
<h2 class="text-xl font-semibold mb-4">Параметры проекта</h2>
|
||||||
|
<div class="space-y-4">
|
||||||
|
<div>
|
||||||
|
<label class="block text-sm font-medium text-gray-700 mb-1">Название проекта</label>
|
||||||
|
<input type="text" id="project-settings-name"
|
||||||
|
class="w-full px-3 py-2 border rounded focus:ring-2 focus:ring-blue-500 focus:border-blue-500">
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label class="block text-sm font-medium text-gray-700 mb-1">Ссылка для "открыть в трекере"</label>
|
||||||
|
<input type="text" id="project-settings-tracker-url" placeholder="https://tracker.example.com/PROJ-123"
|
||||||
|
class="w-full px-3 py-2 border rounded focus:ring-2 focus:ring-blue-500 focus:border-blue-500">
|
||||||
|
<div class="text-xs text-gray-500 mt-1">Оставьте пустым, чтобы скрыть ссылку.</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="flex justify-end space-x-3 mt-6">
|
||||||
|
<button onclick="closeProjectSettingsModal()" class="px-4 py-2 text-gray-600 hover:text-gray-800">Отмена</button>
|
||||||
|
<button onclick="saveProjectSettings()" class="px-4 py-2 bg-blue-600 text-white rounded hover:bg-blue-700">Сохранить</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
const projectUUID = '{{.ProjectUUID}}';
|
const projectUUID = '{{.ProjectUUID}}';
|
||||||
let configStatusMode = 'active';
|
let configStatusMode = 'active';
|
||||||
@@ -397,6 +423,59 @@ function closeImportModal() {
|
|||||||
document.getElementById('import-modal').classList.remove('flex');
|
document.getElementById('import-modal').classList.remove('flex');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function openProjectSettingsModal() {
|
||||||
|
if (!project) return;
|
||||||
|
if (project.is_system) {
|
||||||
|
alert('Системный проект нельзя редактировать');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
document.getElementById('project-settings-name').value = project.name || '';
|
||||||
|
document.getElementById('project-settings-tracker-url').value = (project.tracker_url || '').trim();
|
||||||
|
document.getElementById('project-settings-modal').classList.remove('hidden');
|
||||||
|
document.getElementById('project-settings-modal').classList.add('flex');
|
||||||
|
}
|
||||||
|
|
||||||
|
function closeProjectSettingsModal() {
|
||||||
|
document.getElementById('project-settings-modal').classList.add('hidden');
|
||||||
|
document.getElementById('project-settings-modal').classList.remove('flex');
|
||||||
|
}
|
||||||
|
|
||||||
|
async function saveProjectSettings() {
|
||||||
|
if (!project) return;
|
||||||
|
const name = document.getElementById('project-settings-name').value.trim();
|
||||||
|
const trackerURL = document.getElementById('project-settings-tracker-url').value.trim();
|
||||||
|
if (!name) {
|
||||||
|
alert('Введите название проекта');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const resp = await fetch('/api/projects/' + projectUUID, {
|
||||||
|
method: 'PUT',
|
||||||
|
headers: {'Content-Type': 'application/json'},
|
||||||
|
body: JSON.stringify({name: name, tracker_url: trackerURL})
|
||||||
|
});
|
||||||
|
if (!resp.ok) {
|
||||||
|
if (resp.status === 409) {
|
||||||
|
alert('Проект с таким названием уже существует');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
alert('Не удалось сохранить параметры проекта');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
project = await resp.json();
|
||||||
|
document.getElementById('project-title').textContent = project.name;
|
||||||
|
const trackerLink = document.getElementById('tracker-link');
|
||||||
|
if (trackerLink) {
|
||||||
|
const trackerURLResolved = resolveProjectTrackerURL(project);
|
||||||
|
if (trackerURLResolved) {
|
||||||
|
trackerLink.href = trackerURLResolved;
|
||||||
|
trackerLink.classList.remove('hidden');
|
||||||
|
} else {
|
||||||
|
trackerLink.classList.add('hidden');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
closeProjectSettingsModal();
|
||||||
|
}
|
||||||
|
|
||||||
async function loadImportOptions() {
|
async function loadImportOptions() {
|
||||||
const resp = await fetch('/api/configs?page=1&per_page=500&status=active');
|
const resp = await fetch('/api/configs?page=1&per_page=500&status=active');
|
||||||
if (!resp.ok) return;
|
if (!resp.ok) return;
|
||||||
@@ -480,12 +559,14 @@ document.getElementById('create-modal').addEventListener('click', function(e) {
|
|||||||
document.getElementById('rename-modal').addEventListener('click', function(e) { if (e.target === this) closeRenameModal(); });
|
document.getElementById('rename-modal').addEventListener('click', function(e) { if (e.target === this) closeRenameModal(); });
|
||||||
document.getElementById('clone-modal').addEventListener('click', function(e) { if (e.target === this) closeCloneModal(); });
|
document.getElementById('clone-modal').addEventListener('click', function(e) { if (e.target === this) closeCloneModal(); });
|
||||||
document.getElementById('import-modal').addEventListener('click', function(e) { if (e.target === this) closeImportModal(); });
|
document.getElementById('import-modal').addEventListener('click', function(e) { if (e.target === this) closeImportModal(); });
|
||||||
|
document.getElementById('project-settings-modal').addEventListener('click', function(e) { if (e.target === this) closeProjectSettingsModal(); });
|
||||||
document.addEventListener('keydown', function(e) {
|
document.addEventListener('keydown', function(e) {
|
||||||
if (e.key === 'Escape') {
|
if (e.key === 'Escape') {
|
||||||
closeCreateModal();
|
closeCreateModal();
|
||||||
closeRenameModal();
|
closeRenameModal();
|
||||||
closeCloneModal();
|
closeCloneModal();
|
||||||
closeImportModal();
|
closeImportModal();
|
||||||
|
closeProjectSettingsModal();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -294,6 +294,10 @@ async function createProject() {
|
|||||||
})
|
})
|
||||||
});
|
});
|
||||||
if (!resp.ok) {
|
if (!resp.ok) {
|
||||||
|
if (resp.status === 409) {
|
||||||
|
alert('Проект с таким названием уже существует');
|
||||||
|
return;
|
||||||
|
}
|
||||||
alert('Не удалось создать проект');
|
alert('Не удалось создать проект');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -310,6 +314,10 @@ async function renameProject(projectUUID, currentName) {
|
|||||||
body: JSON.stringify({name: name.trim()})
|
body: JSON.stringify({name: name.trim()})
|
||||||
});
|
});
|
||||||
if (!resp.ok) {
|
if (!resp.ok) {
|
||||||
|
if (resp.status === 409) {
|
||||||
|
alert('Проект с таким названием уже существует');
|
||||||
|
return;
|
||||||
|
}
|
||||||
alert('Не удалось переименовать проект');
|
alert('Не удалось переименовать проект');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -360,6 +368,10 @@ async function copyProject(projectUUID, projectName) {
|
|||||||
body: JSON.stringify({name: newName.trim()})
|
body: JSON.stringify({name: newName.trim()})
|
||||||
});
|
});
|
||||||
if (!createResp.ok) {
|
if (!createResp.ok) {
|
||||||
|
if (createResp.status === 409) {
|
||||||
|
alert('Проект с таким названием уже существует');
|
||||||
|
return;
|
||||||
|
}
|
||||||
alert('Не удалось создать копию проекта');
|
alert('Не удалось создать копию проекта');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user