최초 커밋
This commit is contained in:
@@ -0,0 +1,636 @@
|
||||
<?php
|
||||
require_once $_SERVER['DOCUMENT_ROOT'].'/egbim/skin/member/basic/descope_session.php';
|
||||
require_once $_SERVER['DOCUMENT_ROOT'].'/egbim/bbs/upload_admin_guard.php';
|
||||
|
||||
// 로그인 안 되었거나 관리자 아니면 바로 로그인 화면으로
|
||||
if (empty($_SESSION['user']['userId']) || !is_upload_admin()) {
|
||||
header("Location: /egbim/index.php?popup=login");
|
||||
exit;
|
||||
}
|
||||
?>
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html lang="ko">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>배포 파일 관리 시스템</title>
|
||||
<link href="https://cdn.jsdelivr.net/npm/tailwindcss@2.2.19/dist/tailwind.min.css" rel="stylesheet">
|
||||
<script src="https://cdn.tailwindcss.com"></script>
|
||||
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
|
||||
</head>
|
||||
<body class="bg-gray-100 p-6">
|
||||
<div class="max-w-screen-xl mx-auto">
|
||||
<div class="flex items-center mb-4">
|
||||
<svg class="w-7 h-7 text-blue-600 mr-2" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M4 16v2a2 2 0 002 2h12a2 2 0 002-2v-2M7 10l5-5m0 0l5 5m-5-5v12" />
|
||||
</svg>
|
||||
<span class="text-xl font-bold mr-4">한맥 가족사 S/W 업로드</span>
|
||||
</div>
|
||||
|
||||
<div class="bg-white p-4 rounded shadow mb-6 flex items-center justify-between">
|
||||
<div class="flex items-center space-x-4">
|
||||
<label for="product-select" class="font-bold">제품명(제품코드):</label>
|
||||
<select id="product-select" class="p-2 border border-gray-300 rounded">
|
||||
<option value="1">EG-BIM</option>
|
||||
<option value="2">TOVA</option>
|
||||
<option value="3">GAIA</option>
|
||||
<option value="4">EG-BIM-MODELER</option>
|
||||
<option value="5">EG-BIM_VIEWER</option>
|
||||
<option value="999" selected>TEST</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="flex items-center space-x-4">
|
||||
<div>
|
||||
<span class="text-sm">내부 배포 버전(최신):</span>
|
||||
<span id="latest-family" class="text-blue-600 font-semibold">0.0.0</span>
|
||||
</div>
|
||||
<div class="flex items-center space-x-1">
|
||||
<span class="text-sm">외부 배포 버전:</span>
|
||||
<select id="external-version" class="p-1 border rounded">
|
||||
<option value="">사용안함</option>
|
||||
</select>
|
||||
<button id="save-version" class="bg-blue-500 text-white px-3 py-1 rounded hover:bg-blue-600">저장</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex justify-between items-center mb-4">
|
||||
<div class="flex items-center space-x-2">
|
||||
<input type="checkbox" id="show-deleted" class="mr-1">
|
||||
<label for="show-deleted" class="text-sm">삭제된 버전 표시</label>
|
||||
</div>
|
||||
|
||||
<div class="flex space-x-2">
|
||||
<button id="btn-new" class="bg-green-500 text-white px-3 py-1 rounded hover:bg-green-600">+ 신규 버전</button>
|
||||
<button class="bg-red-500 text-white px-3 py-1 rounded hover:bg-red-600" id="btn-delete">파일삭제</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="bg-white shadow rounded-lg overflow-x-auto max-h-[70vh] overflow-y-auto">
|
||||
<table class="min-w-full text-sm text-left">
|
||||
<thead class="bg-gray-200 sticky top-0 z-10">
|
||||
<tr>
|
||||
<th class="px-2 py-2 text-center w-[4%]"></th>
|
||||
<th class="px-2 py-2 text-center w-[7%] whitespace-nowrap">버전</th>
|
||||
<th class="px-2 py-2 text-center w-[20%] whitespace-nowrap">파일명</th>
|
||||
<th class="px-2 py-2 text-center w-[8%] whitespace-nowrap">파일타입</th>
|
||||
<th class="px-2 py-2 text-center w-[8%] whitespace-nowrap">크기</th>
|
||||
<th class="px-2 py-2 text-center w-[12%] whitespace-nowrap">업로드일</th>
|
||||
<th class="px-2 py-2 text-center w-[10%] whitespace-nowrap">업로드수행자</th>
|
||||
<th class="px-2 py-2 text-center w-[12%] whitespace-nowrap">테스트완료</th>
|
||||
<th class="px-2 py-2 text-center w-[10%] whitespace-nowrap">테스트수행자</th>
|
||||
<th class="px-2 py-2 text-center w-[12%] whitespace-nowrap">릴리즈일</th>
|
||||
<th class="px-2 py-2 text-center w-[10%] whitespace-nowrap">릴리즈수행자</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="file-list-1" class="file-list hidden"></tbody>
|
||||
<tbody id="file-list-2" class="file-list hidden"></tbody>
|
||||
<tbody id="file-list-3" class="file-list hidden"></tbody>
|
||||
<tbody id="file-list-4" class="file-list hidden"></tbody>
|
||||
<tbody id="file-list-5" class="file-list hidden"></tbody>
|
||||
<tbody id="file-list-999" class="file-list"></tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="upload-modal" class="fixed inset-0 bg-black bg-opacity-40 flex items-center justify-center z-50 hidden">
|
||||
<div class="bg-white p-8 rounded shadow-lg w-full max-w-lg relative">
|
||||
<h2 class="text-xl font-bold mb-6 text-center">신규 버전 업로드</h2>
|
||||
|
||||
<div class="space-y-3">
|
||||
<div class="flex items-center">
|
||||
<label class="w-28">S/W 버전 :</label>
|
||||
<input type="number" class="border w-14 p-1 mx-1 text-center" id="sw1" min="0" max="99" required>
|
||||
<span>.</span>
|
||||
<input type="number" class="border w-14 p-1 mx-1 text-center" id="sw2" min="0" max="99" required>
|
||||
<span>.</span>
|
||||
<input type="number" class="border w-14 p-1 mx-1 text-center" id="sw3" min="0" max="999" required>
|
||||
<span id="version-type-text" class="ml-3 font-bold text-gray-600">Major</span>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center mb-2">
|
||||
<label class="w-36">설치파일(EXE):</label>
|
||||
<input type="file" class="hidden" id="setup-file" accept=".exe">
|
||||
<input type="text" class="border flex-1 p-1 bg-gray-100 ml-2" id="setup-filename" readonly>
|
||||
<button type="button" class="ml-2 px-3 py-1 bg-blue-500 text-white rounded" id="setup-add">추가</button>
|
||||
</div>
|
||||
<div class="flex items-center mb-2">
|
||||
<label class="w-36">패치파일(EXE):</label>
|
||||
<input type="file" class="hidden" id="patch-file" accept=".exe">
|
||||
<input type="text" class="border flex-1 p-1 bg-gray-100 ml-2" id="patch-filename" readonly>
|
||||
<button type="button" class="ml-2 px-3 py-1 bg-blue-500 text-white rounded" id="patch-add">추가</button>
|
||||
</div>
|
||||
<div class="flex items-center mb-2">
|
||||
<label class="w-36">릴리즈노트(MD):</label>
|
||||
<input type="file" class="hidden" id="release-file" accept=".md">
|
||||
<input type="text" class="border flex-1 p-1 bg-gray-100 ml-2" id="release-filename" readonly>
|
||||
<button type="button" class="ml-2 px-3 py-1 bg-blue-500 text-white rounded" id="release-add">추가</button>
|
||||
</div>
|
||||
<div class="flex items-center mb-2">
|
||||
<label class="w-36">정보파일(ZIP):</label>
|
||||
<input type="file" class="hidden" id="info-file">
|
||||
<input type="text" class="border flex-1 p-1 bg-gray-100 ml-2" id="info-filename" readonly>
|
||||
<button type="button" class="ml-2 px-3 py-1 bg-blue-500 text-white rounded" id="info-add">추가</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex justify-end space-x-2 mt-6">
|
||||
<button id="upload-submit" class="bg-blue-500 text-white px-4 py-2 rounded hover:bg-blue-600">업로드</button>
|
||||
<button id="upload-cancel" class="bg-gray-400 text-white px-4 py-2 rounded hover:bg-gray-500">취소</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// 팝업 열기/닫기
|
||||
$('#btn-new').on('click', ()=>$('#upload-modal').removeClass('hidden'));
|
||||
$('#upload-cancel').on('click', ()=>$('#upload-modal').addClass('hidden'));
|
||||
$('#upload-modal').on('click', function(e){ if(e.target===this) $(this).addClass('hidden'); });
|
||||
|
||||
// 파일 버튼
|
||||
$('#setup-add').on('click', ()=>$('#setup-file').click());
|
||||
$('#patch-add').on('click', ()=>$('#patch-file').click());
|
||||
$('#info-add').on('click', ()=>$('#info-file').click());
|
||||
$('#release-add').on('click', ()=>$('#release-file').click());
|
||||
|
||||
// 파일명 표시
|
||||
$('#setup-file').on('change', function(){ $('#setup-filename').val(this.files[0]?.name || ''); });
|
||||
$('#patch-file').on('change', function(){ $('#patch-filename').val(this.files[0]?.name || ''); });
|
||||
$('#info-file').on('change', function(){ $('#info-filename').val(this.files[0]?.name || ''); });
|
||||
$('#release-file').on('change', function(){ $('#release-filename').val(this.files[0]?.name || ''); });
|
||||
|
||||
function getCurrentUserName() {
|
||||
return window.currentUserName || "알 수 없음";
|
||||
}
|
||||
|
||||
// 업로드 실행
|
||||
$('#upload-submit').on('click', async function() {
|
||||
const productCode = $('#product-select').val();
|
||||
const swVersion = [$('#sw1').val(), $('#sw2').val(), $('#sw3').val()].join('.');
|
||||
const versionType = $('#version-type-text').text().toLowerCase();
|
||||
const sw1 = $('#sw1').val();
|
||||
const sw2 = $('#sw2').val();
|
||||
const sw3 = $('#sw3').val();
|
||||
|
||||
if (!sw1 || !sw2 || !sw3) {
|
||||
alert("S/W 버전을 모두 입력하세요.");
|
||||
return;
|
||||
}
|
||||
|
||||
const setupFile = $('#setup-file')[0].files[0];
|
||||
const patchFile = $('#patch-file')[0].files[0];
|
||||
const infoFile = $('#info-file')[0].files[0];
|
||||
const releaseFile = $('#release-file')[0].files[0];
|
||||
|
||||
if (!setupFile) {
|
||||
alert("설치파일(EXE)은 반드시 업로드해야 합니다.");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
let checkRes = await $.post("/egbim/sw_upload/upload_check_version.php", {
|
||||
product_code: productCode,
|
||||
sw_version: swVersion
|
||||
});
|
||||
|
||||
if (checkRes.exists) {
|
||||
const proceed = confirm(`기존에 업로드된 [${swVersion}] 버전이 존재합니다.\n파일을 덮어쓰겠습니까?`);
|
||||
if (!proceed) return;
|
||||
}
|
||||
} catch (err) {
|
||||
console.error("버전 확인 오류:", err);
|
||||
alert("버전 확인 중 오류가 발생했습니다.");
|
||||
return;
|
||||
}
|
||||
|
||||
// ✅ 파일 규칙 이름 롤백 (텍스트 변환 제거, 원래대로 숫자 코드 사용)
|
||||
const rename = (type, ext) => {
|
||||
// 제품 코드와 제품명 매핑 (DB/서버 폴더 구조와 일치해야 함)
|
||||
const productMap = {
|
||||
"1": "eg-bim",
|
||||
"2": "tova",
|
||||
"3": "gaia",
|
||||
"4": "eg-bim-modeler", // 수정된 부분: EG-BIM-MODELER 파일명 규칙 매핑 추가
|
||||
"5": "eg-bim-viewer",
|
||||
"999": "test"
|
||||
};
|
||||
|
||||
const prodName = productMap[productCode] || productCode; // 매핑 없으면 코드 그대로 사용
|
||||
const ver = swVersion.toLowerCase();
|
||||
|
||||
switch (type) {
|
||||
case "setup": return `setup_${prodName}_${ver}${ext}`;
|
||||
case "patch": return `patch_${prodName}_${ver}${ext}`;
|
||||
case "info": return `updateinfo_${prodName}_${ver}${ext}`;
|
||||
case "md": return `releasenote_${prodName}_${ver}${ext}`;
|
||||
}
|
||||
};
|
||||
|
||||
const files = [];
|
||||
if (setupFile) files.push({ orig: setupFile, type:"setup", ext:".exe" });
|
||||
if (patchFile) files.push({ orig: patchFile, type:"patch", ext:".exe" });
|
||||
if (infoFile) files.push({ orig: infoFile, type:"info", ext:"." + infoFile.name.split(".").pop() });
|
||||
if (releaseFile) files.push({ orig: releaseFile, type:"md", ext:".md" });
|
||||
|
||||
try {
|
||||
for (let f of files) {
|
||||
const renamed = rename(f.type, f.ext);
|
||||
|
||||
let formData = new FormData();
|
||||
formData.append("product_code", productCode);
|
||||
formData.append("sw_version", swVersion);
|
||||
formData.append("filename", renamed);
|
||||
formData.append("content_type", f.orig.type || "application/octet-stream");
|
||||
|
||||
let presignRes = await fetch("/egbim/sw_upload/upload_presign.php", { method:"POST", body:formData });
|
||||
let presignData = await presignRes.json();
|
||||
|
||||
let putRes = await fetch(presignData.url, {
|
||||
method:"PUT",
|
||||
headers:{ "Content-Type": f.orig.type || "application/octet-stream" },
|
||||
body:f.orig
|
||||
});
|
||||
if (!putRes.ok) throw new Error(renamed+" 업로드 실패");
|
||||
|
||||
f.path = presignData.key;
|
||||
f.name = renamed;
|
||||
f.size = f.orig.size;
|
||||
f.filetype = f.type;
|
||||
}
|
||||
|
||||
const userName = sessionStorage.getItem("userName") || "알 수 없음";
|
||||
|
||||
$.post("/egbim/sw_upload/upload_popup.php", {
|
||||
product_code: productCode,
|
||||
sw_version: swVersion,
|
||||
version_type: versionType,
|
||||
uploader: userName,
|
||||
files: JSON.stringify(files.map(f=>({
|
||||
name: f.name,
|
||||
original: f.orig.name,
|
||||
path: f.path,
|
||||
type: f.filetype,
|
||||
size: f.size
|
||||
})))
|
||||
}, function(res){
|
||||
if(res.group){
|
||||
loadFileList(productCode);
|
||||
alert("업로드 및 DB 저장 성공!");
|
||||
}
|
||||
}, "json");
|
||||
|
||||
$('#upload-modal').addClass('hidden');
|
||||
|
||||
} catch(err){
|
||||
alert("업로드 중 오류: "+err.message);
|
||||
}
|
||||
});
|
||||
|
||||
$(document).ready(function(){
|
||||
const productCode = $('#product-select').val();
|
||||
loadFileList(productCode);
|
||||
loadVersion(productCode);
|
||||
|
||||
$('#product-select').on('change', function(){
|
||||
const newCode = $(this).val();
|
||||
loadFileList(newCode);
|
||||
loadVersion(newCode);
|
||||
});
|
||||
});
|
||||
|
||||
function loadFileList(productCode){
|
||||
const showDeleted = $("#show-deleted").is(":checked") ? 1 : 0;
|
||||
|
||||
$.ajax({
|
||||
url: "/egbim/sw_upload/upload_list.php",
|
||||
type: "GET",
|
||||
data: { product_code: productCode, show_deleted: showDeleted },
|
||||
dataType: "json",
|
||||
success: function(res){
|
||||
const $tbody = $("#file-list-" + productCode);
|
||||
$tbody.empty();
|
||||
|
||||
if(res.status !== "ok"){
|
||||
$tbody.append(`<tr><td colspan="11" class="text-center py-4 text-red-500">조회 실패: ${res.message}</td></tr>`);
|
||||
return;
|
||||
}
|
||||
|
||||
if(res.files && res.files.length > 0){
|
||||
renderFilesFromDB(res.files, $tbody);
|
||||
} else {
|
||||
$tbody.append(`<tr><td colspan="11" class="text-center py-4 text-gray-500">데이터가 없습니다.</td></tr>`);
|
||||
}
|
||||
},
|
||||
error: function(xhr){
|
||||
alert("리스트 조회 중 오류가 발생했습니다. (code: "+xhr.status+")");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
$(document).on("change", "#show-deleted", function(){
|
||||
const productCode = $("#product-select").val();
|
||||
loadFileList(productCode);
|
||||
});
|
||||
|
||||
function renderFilesFromDB(files, $tbody){
|
||||
let grouped = {};
|
||||
files.forEach(f=>{
|
||||
let key = f.sw_version+"_"+f.upload_date;
|
||||
if(!grouped[key]) grouped[key] = {meta:f, files:[]};
|
||||
grouped[key].files.push(f);
|
||||
});
|
||||
|
||||
Object.values(grouped).forEach(g=>{
|
||||
let rowspan = g.files.length;
|
||||
g.files.forEach((f, idx)=>{
|
||||
// ✅ 직접 다운로드 API 주소 생성 (filepath와 filename을 인자로 전달)
|
||||
const downloadUrl = `/egbim/sw_upload/upload_download.php?path=${encodeURIComponent(f.filepath)}&name=${encodeURIComponent(f.filename)}`;
|
||||
|
||||
$tbody.append(`
|
||||
<tr class="border-b">
|
||||
${idx===0 ? `
|
||||
<td class="px-4 py-2 text-center ${f.deleted==1?'bg-red-100':''}" rowspan="${rowspan}">
|
||||
${f.deleted==1 ? '' : `<input type="checkbox" value="${f.product_code}|${f.sw_version}"/>`}
|
||||
</td>
|
||||
<td class="px-4 py-2 font-bold text-blue-600 text-center" rowspan="${rowspan}">
|
||||
${f.sw_version}
|
||||
</td>
|
||||
` : ''}
|
||||
|
||||
<td class="px-4 py-2 ${f.deleted==1?'bg-red-100':''}">
|
||||
<a href="${downloadUrl}" class="text-blue-600 hover:text-blue-800 hover:underline flex items-center" target="_blank">
|
||||
<i class="fa-solid fa-file-arrow-down mr-2 opacity-70"></i>
|
||||
${f.filename}
|
||||
</a>
|
||||
</td>
|
||||
|
||||
<td class="px-4 py-2 text-center ${f.deleted==1?'bg-red-100':''}">${f.filetype}</td>
|
||||
<td class="px-4 py-2 text-center ${f.deleted==1?'bg-red-100':''}">
|
||||
${f.filetype==="md"
|
||||
? (Math.round(f.filesize/1024*100)/100)+"KB"
|
||||
: (Math.round(f.filesize/1024/1024*100)/100)+"MB"}
|
||||
</td>
|
||||
|
||||
${idx===0?`
|
||||
<td class="px-4 py-2 text-center" rowspan="${rowspan}">${f.upload_date}</td>
|
||||
<td class="px-4 py-2 text-center" rowspan="${rowspan}">${f.uploader}</td>
|
||||
<td class="px-4 py-2 text-center" rowspan="${rowspan}">
|
||||
${f.deleted==1
|
||||
? '<span class="text-red-500">삭제됨</span>'
|
||||
: (f.test_done_date
|
||||
? f.test_done_date
|
||||
: `<button class="bg-green-500 text-white px-2 py-1 rounded btn-testdone"
|
||||
data-product="${f.product_code}"
|
||||
data-version="${f.sw_version}">완료확정</button>`)}
|
||||
</td>
|
||||
<td class="px-4 py-2 text-center" rowspan="${rowspan}">${f.tester || ''}</td>
|
||||
<td class="px-4 py-2 text-center" rowspan="${rowspan}">
|
||||
${f.deleted==1
|
||||
? '<span class="text-red-500">삭제됨</span>'
|
||||
: (f.release_date
|
||||
? f.release_date
|
||||
: `<button class="bg-blue-500 text-white px-2 py-1 rounded btn-release"
|
||||
data-product="${f.product_code}"
|
||||
data-version="${f.sw_version}">배포확정</button>`)}
|
||||
</td>
|
||||
<td class="px-4 py-2 text-center" rowspan="${rowspan}">${f.releaser || ''}</td>
|
||||
`:''}
|
||||
</tr>
|
||||
`);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
$('#product-select').on('change', function(){
|
||||
const newCode = $(this).val();
|
||||
$(".file-list").addClass("hidden");
|
||||
$("#file-list-" + newCode).removeClass("hidden");
|
||||
loadFileList(newCode);
|
||||
loadVersion(newCode);
|
||||
});
|
||||
|
||||
function updateVersionType() {
|
||||
const major = parseInt($('#sw1').val() || 0, 10);
|
||||
const minor = parseInt($('#sw2').val() || 0, 10);
|
||||
const patch = parseInt($('#sw3').val() || 0, 10);
|
||||
|
||||
let typeText = 'MAJOR';
|
||||
if (patch > 0) {
|
||||
typeText = 'PATCH';
|
||||
} else if (minor > 0) {
|
||||
typeText = 'MINOR';
|
||||
} else if (major > 0) {
|
||||
typeText = 'MAJOR';
|
||||
}
|
||||
|
||||
$('#version-type-text').text(typeText);
|
||||
|
||||
if (patch > 0) {
|
||||
$('#patch-file').prop('disabled', false);
|
||||
$('#patch-filename').prop('disabled', false);
|
||||
$('#patch-add').prop('disabled', false).removeClass('bg-gray-400').addClass('bg-blue-500');
|
||||
} else {
|
||||
$('#patch-file').prop('disabled', true).val('');
|
||||
$('#patch-filename').prop('disabled', true).val('');
|
||||
$('#patch-add').prop('disabled', true).removeClass('bg-blue-500').addClass('bg-gray-400');
|
||||
}
|
||||
}
|
||||
|
||||
$('#sw1, #sw2, #sw3').on('input', updateVersionType);
|
||||
updateVersionType();
|
||||
|
||||
$(document).on('click', '.btn-testdone', function(){
|
||||
const productCode = $(this).data('product');
|
||||
const swVersion = $(this).data('version');
|
||||
const userName = sessionStorage.getItem("userName") || "알 수 없음";
|
||||
|
||||
$.post("/egbim/sw_upload/upload_testdone.php", {
|
||||
product_code: productCode,
|
||||
sw_version: swVersion,
|
||||
tester: userName
|
||||
}, function(res){
|
||||
if(res.test_done_date){
|
||||
$(`#file-list tr`).each(function(){
|
||||
const version = $(this).find("td:nth-child(2)").text().trim();
|
||||
if(version === swVersion){
|
||||
$(this).find("td").eq(7).text(res.test_done_date);
|
||||
$(this).find("td").eq(8).text(res.tester);
|
||||
}
|
||||
});
|
||||
alert("테스트 완료되었습니다.");
|
||||
loadFileList(productCode);
|
||||
}
|
||||
}, "json");
|
||||
});
|
||||
|
||||
$(document).on("click", ".btn-release", function(){
|
||||
const productCode = $(this).data("product");
|
||||
const swVersion = $(this).data("version");
|
||||
const userName = sessionStorage.getItem("userName") || "알 수 없음";
|
||||
|
||||
// ✅ 1. 사용자에게 제시할 확인 문구 설정
|
||||
const confirmText = "배포 완료 요청";
|
||||
|
||||
// ✅ 2. 입력창(prompt) 띄우기
|
||||
const userInput = prompt(`[버전 ${swVersion}] 릴리즈 서버로 이동하시겠습니까?\n\n진행하시려면 하단에 "${confirmText}" 문구를 정확히 입력해주세요.`);
|
||||
|
||||
// ✅ 3. 검증 로직
|
||||
if (userInput === null) return; // '취소'를 누른 경우
|
||||
|
||||
if (userInput.trim() !== confirmText) {
|
||||
alert(`입력한 문구가 일치하지 않습니다.\n(입력: ${userInput})`);
|
||||
return;
|
||||
}
|
||||
|
||||
// ✅ 4. 문구가 일치할 경우에만 기존 서버 통신(AJAX) 실행
|
||||
$.post("/egbim/sw_upload/upload_release.php", {
|
||||
product_code: productCode,
|
||||
sw_version: swVersion,
|
||||
releaser: userName
|
||||
}, function(res){
|
||||
if(res.release_date){
|
||||
$(`#file-list tr`).each(function(){
|
||||
const version = $(this).find("td:nth-child(2)").text().trim();
|
||||
if(version === swVersion){
|
||||
$(this).find("td").eq(9).text(res.release_date);
|
||||
$(this).find("td").eq(10).text(res.releaser);
|
||||
}
|
||||
});
|
||||
alert(`[버전 ${swVersion}] 배포가 성공적으로 확정되었습니다.`);
|
||||
loadFileList(productCode);
|
||||
} else {
|
||||
alert("실패: " + (res.message || "배포 확정 처리 중 오류 발생"));
|
||||
}
|
||||
}, "json");
|
||||
});
|
||||
|
||||
$(document).on("click", "#btn-delete", function () {
|
||||
const checked = $("input[type=checkbox]:checked").map(function () { return $(this).val(); }).get();
|
||||
if (checked.length === 0) {
|
||||
alert("삭제할 버전을 선택하세요.");
|
||||
return;
|
||||
}
|
||||
if (!confirm("선택한 버전 전체 파일을 삭제하시겠습니까?")) return;
|
||||
|
||||
checked.forEach(item => {
|
||||
const [product, version] = item.split("|");
|
||||
$.post("/egbim/sw_upload/upload_delete.php", {
|
||||
product_code: product,
|
||||
sw_version: version
|
||||
}, function(res) {
|
||||
if (res.status === "ok") {
|
||||
alert(`[${version}] 삭제 완료!`);
|
||||
loadFileList(product);
|
||||
} else {
|
||||
if (res.failed && res.failed.length > 0) {
|
||||
let errors = res.failed.map(f => `${f.file}: ${f.error}`).join("\n");
|
||||
alert("삭제 실패:\n" + errors);
|
||||
} else {
|
||||
alert("삭제 실패: " + (res.message || "알 수 없는 오류"));
|
||||
}
|
||||
}
|
||||
}, "json");
|
||||
});
|
||||
});
|
||||
|
||||
// ✅ 저장/변경 버튼 클릭 이벤트 수정
|
||||
$(document).on("click", "#save-version", function() {
|
||||
const productCode = $("#product-select").val();
|
||||
const version = $("#external-version").val(); // "사용안함" 선택 시 "" 값이 전달됨
|
||||
|
||||
const msg = (version && version !== "")
|
||||
? `외부 배포를 [ ${version} ] 버전으로 변경하시겠습니까?`
|
||||
: `외부 배포를 '사용안함'으로 설정하시겠습니까?`;
|
||||
|
||||
if (!confirm(msg)) return;
|
||||
|
||||
$.post("/egbim/sw_upload/upload_version_update.php", {
|
||||
product_code: productCode,
|
||||
version: version // 백엔드에서 ""를 받으면 내부 버전과 동일하게 맞춰 "사용안함" 처리함
|
||||
}, function(res) {
|
||||
if (res.status === "ok") {
|
||||
alert("배포 버전 설정이 완료되었습니다.");
|
||||
loadVersion(productCode); // UI 새로고침
|
||||
} else {
|
||||
alert("설정 실패: " + res.message);
|
||||
}
|
||||
}, "json");
|
||||
});
|
||||
|
||||
// ✅ 외부 배포 버전 정보 및 선택 목록 로드 함수 수정
|
||||
function loadVersion(productCode) {
|
||||
$.getJSON("/egbim/sw_upload/upload_version_get.php", { product_code: productCode }, function(res) {
|
||||
if (res.status === "ok") {
|
||||
// ✅ 이 코드가 있어야 "0.0.0" 대신 "1.2.0" 등의 실제 버전이 화면에 보입니다.
|
||||
$("#latest-family").text(res.family_version);
|
||||
|
||||
const $sel = $("#external-version");
|
||||
$sel.empty();
|
||||
|
||||
// 목록 그리기
|
||||
if (res.confirmed_versions) {
|
||||
res.confirmed_versions.forEach(v => {
|
||||
const label = (v === "") ? "사용안함" : v;
|
||||
$sel.append(`<option value="${v}">${label}</option>`);
|
||||
});
|
||||
}
|
||||
|
||||
$sel.val(res.external_version);
|
||||
|
||||
if (res.external_version !== "") {
|
||||
$("#save-version").text("변경").removeClass("bg-blue-500").addClass("bg-yellow-500");
|
||||
} else {
|
||||
$("#save-version").text("저장").removeClass("bg-yellow-500").addClass("bg-blue-500");
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function bindRangeCorrection(selector) {
|
||||
$(selector).on("input", function() {
|
||||
let val = parseInt($(this).val(), 10);
|
||||
let min = parseInt($(this).attr("min"), 10) || 0;
|
||||
let max = parseInt($(this).attr("max"), 10);
|
||||
|
||||
if (isNaN(val)) {
|
||||
$(this).val("");
|
||||
} else if (val < min) {
|
||||
$(this).val(min);
|
||||
} else if (val > max) {
|
||||
$(this).val(max);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
bindRangeCorrection("#sw1, #sw2, #sw3");
|
||||
|
||||
$('#btn-new').on('click', ()=>{
|
||||
$('#setup-file, #patch-file, #release-file, #info-file').val('');
|
||||
$('#setup-filename, #patch-filename, #release-filename, #info-filename').val('');
|
||||
|
||||
const latestVer = $('#latest-family').text().trim();
|
||||
if (latestVer) {
|
||||
const parts = latestVer.split('.');
|
||||
$('#sw1').val(parts[0] || 0);
|
||||
$('#sw2').val(parts[1] || 0);
|
||||
$('#sw3').val(parts[2] || 0);
|
||||
}
|
||||
|
||||
const patch = parseInt($('#sw3').val() || 0, 10);
|
||||
if (patch > 0) {
|
||||
$('#patch-file').prop('disabled', false);
|
||||
$('#patch-filename').prop('disabled', false);
|
||||
$('#patch-add').prop('disabled', false).removeClass('bg-gray-400').addClass('bg-blue-500');
|
||||
} else {
|
||||
$('#patch-file').prop('disabled', true).val('');
|
||||
$('#patch-filename').prop('disabled', true).val('');
|
||||
$('#patch-add').prop('disabled', true).removeClass('bg-blue-500').addClass('bg-gray-400');
|
||||
}
|
||||
|
||||
updateVersionType();
|
||||
$('#upload-modal').removeClass('hidden');
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user